没有程序集的C / C ++函数定义 [英] C/C++ function definitions without assembly

查看:145
本文介绍了没有程序集的C / C ++函数定义的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我一直认为在最后一步使用内联汇编定义了 printf()这样的函数。这深深的stdio.h的肠子埋了一些asm代码,实际上告诉CPU做什么。例如,在dos中,我记得它是通过第一个 mov 将字符串的开头实现到某个内存位置或寄存器,而不是调用 int



然而,由于x64版本的Visual Studio不支持内联汇编程序,所以我不知道如何可以在C / C ++中根本没有汇编器定义的函数。如何在C / C ++中不使用汇编代码实现 printf()的库函数?什么实际执行正确的软件中断?谢谢。

解决方案

橡胶必须满足点。但是有一个很多的图层,才能找到那个地方!这听起来像你有一些基于DOS日的先入为主,这不太相关了。



这里有一些好的一般点,但没有人链接到精确的魔鬼在源的细节。所以为了让你足够抱歉,你问:)我做了一个彻底的跟踪的 printf 故事 GNU的libc和Linux 不要对任何步骤挥手。在这个过程中,我带来了一些我自己的知识更新(警告:这不是很容易无聊!):



(原来的链接是http://blog.hostilefork.com/where-printf-rubber-meets-road/ ,它会但



第一步



ll当然从printf的原型开始,它在文件 libc / libio / stdio.h

中定义

  extern int printf(__const char * __ restrict __format,...); 

但是,你不会找到一个名为printf的函数的源代码。相反,在文件 /libc/stdio-common/printf.c 中,您会发现一些与一个名为 __ printf

  int __printf(const char * format,...)
{
va_list arg;
int done;

va_start(arg,format);
done = vfprintf(stdout,format,arg);
va_end(arg);

return done;
}

同一文件中的宏设置了一个关联,作为非下划线printf的别名:

  ldbl_strong_alias(__printf,printf); 

printf是一个很薄的层,用stdout调用vfprintf。实际上,格式化工作的核心是在vfprintf中完成,您可以在 libc / stdio-common / vfprintf.c 中找到它。这是一个很长的功能,但你可以看到它仍然在C!



深入下来兔子洞...



vfprintf神秘地调用outchar和outstring,这是在同一个文件中定义的奇怪的宏:

  #define outchar \ 
do \
{\
register const INT_T outc =(Ch); \
if(PUTC(outc,s)== EOF || done == INT_MAX)\
{\
done = -1; \
goto all_done; \
} \
++ done; \
} \
while(0)

为什么这么奇怪,我们看到它依赖于神秘的PUTC,也在同一个文件:

  #define PUTC ,F)IO_putwc_unlocked(C,F)

当你得到 IO_putwc_unlocked libc / libio / libio.h 中,您可能会开始认为您不再关心printf是如何工作的:

  #define _IO_putwc_unlocked(_wch,_fp)\ 
(_IO_BE((_fp) - > _wide_data-> _IO_write_ptr \
> =(_fp) - > _wide_data-> _IO_write_end,0)\
?__woverflow(_fp,_wch)\
:(_IO_wint_t) _wide_data-> _IO_write_ptr ++ =(_wch)))

但是尽管有点难以阅读,只是做缓冲输出。如果文件指针的缓冲区中有足够的空间,那么它只会将字符粘贴到其中...但是如果没有,则调用 __ woverflow 。因为你已经用尽缓冲区的唯一选择是冲洗到屏幕(或者你的文件指针表示的任何设备),我们可以希望找到神奇的咒语。



Vtables in C?



如果你猜想我们要跳过另一个令人沮丧的间接层次,你会是对的。查看libc / libio / wgenops.c,你会发现 __ woverflow 的定义:

  wint_t 
__woverflow(f,wch)
_IO_FILE * f;
wint_t wch;
{
if(f-> _mode == 0)
_IO_fwide(f,1);
return _IO_OVERFLOW(f,wch);
}

基本上,文件指针在GNU标准库中实现为对象。它们有数据成员,但也有函数成员,你可以使用JUMP宏的变体来调用它们。在文件 libc / libio / libioP.h 中,您会发现一些有关此技术的文档:

  / *跳转功能。 

* _IO_FILE类型用于实现GNU libc,
*中的FILE类型,以及用于C ++的GNU iostreams中的streambuf类。
*这些都是相同的,只是使用不同。
*允许一个_IO_FILE(或FILE)对象,后跟一个指向
*的跳转表(指向函数的指针)。使用_IO_JUMPS宏访问指针
*。跳转表具有偏心格式
*,以便与C ++虚拟函数表的布局兼容。
*(由g ++实现)。当指向streambuf对象的指针是
*强制到(_IO_FILE *),那么对结果只有
*的_IO_JUMPS恰好指向streambuf的虚函数表。
*因此,用于C stdio / libio的_IO_JUMPS函数表将
* double duty用作C ++ streambuf的虚函数表。
*
*下面描述_IO_JUMPS函数表(以及streambuf的
* virtual functions)中的条目。
*每个函数入口的第一个参数是被作用的_IO_FILE / streambuf
*对象(即'this'参数)。
* /

所以当我们找到 IO_OVERFLOW libc / libio / genops.c ,我们发现它是一个宏,它调用1-参数__overflow 方法对文件指针:

  #define IO_OVERFLOW(FP,CH)JUMP1(__overflow,FP,CH)

各种文件指针类型的跳转表位于libc / libio / fileops.c

  const struct _IO_jump_t _IO_file_jumps = 
{
JUMP_INIT_DUMMY,
JUMP_INIT(finish,INTUSE(_IO_file_finish)),
JUMP_INIT(overflow,INTUSE(_IO_file_overflow)),
JUMP_INIT(underflow,INTUSE(_IO_file_underflow)),
JUMP_INIT(uflow,INTUSE(_IO_default_uflow)),
JUMP_INIT(pbackfail,INTUSE (_IO_default_pbackfail)),
JUMP_INIT(xsputn,INTUSE(_IO_file_xsputn)),
JUMP_INIT(xsgetn,INTUSE(_IO_file_xsgetn)),
JUMP_INIT(seekoff,_IO_new_file_seekoff),
JUMP_INIT seek,_IO_default_seekpos),
JUMP_INIT(setbuf,_IO_new_file_setbuf),
JUMP_INIT(sync,_IO_new_file_sync),
JUMP_INIT(doallocate,INTUSE(_IO_file_doallocate)),
JUMP_INIT (_IO_file_read)),
JUMP_INIT(write,_IO_new_file_write),
JUMP_INIT(seek,INTUSE(_IO_file_seek)),
JUMP_INIT(close,INTUSE(_IO_file_close)),
JUMP_INIT stat,INTUSE(_IO_file_stat)),
JUMP_INIT(showmanyc,_IO_default_showmanyc),
JUMP_INIT(imbue,_IO_default_imbue)
};
libc_hidden_​​data_def(_IO_file_jumps)

还有一个#define等同于 _IO_new_file_overflow _IO_file_overflow ,前者在同一个源文件中定义。 (注意:INTUSE只是一个标记内部使用的函数的宏,它不意味着像这个函数使用中断)。



_IO_new_file_overflow的源代码做了一堆更多的缓冲操作,但它确实调用 _IO_do_flush : / p>

  #define _IO_do_flush(_f)\ 
INTUSE(_IO_do_write)(_ f,(_f) - > _IO_write_base, \
(_f) - > _IO_write_ptr - (_ f) - > _IO_write_base)

我们现在处于一个点,其中_IO_do_write可能是橡胶实际上遇到的道路:无缓冲的,实际的,直接写入I / O设备。至少我们可以希望!它由宏映射到_IO_new_do_write,我们有这样的:

  static 
_IO_size_t
new_do_write ,data,to_do)
_IO_FILE * fp;
const char * data;
_IO_size_t to_do;
{
_IO_size_t count;
if(fp-> _flags& _IO_IS_APPENDING)
/ *在没有适当O_APPEND实现的系统上,
在这里需要sys_seek(0,SEEK_END),但是$ b对于类Unix或Posix系统,$ b不是必需的,也不是所希望的。
相反,只是表明偏移量(前后)是
不可预测的。 * /
fp-> _offset = _IO_pos_BAD;
else if(fp-> _IO_read_end!= fp-> _IO_write_base)
{
_IO_off64_t new_pos
= _IO_SYSSEEK(fp,fp-> _IO_write_base - fp-> _IO_read_end,1);
if(new_pos == _IO_pos_BAD)
return 0;
fp-> _offset = new_pos;
}
count = _IO_SYSWRITE(fp,data,to_do);
if(fp-> _cur_column& amp; count)
fp-> _cur_column = INTUSE(_IO_adjust_column)(fp-> _cur_column - 1,data,
count)+ 1 ;
_IO_setg(fp,fp-> _IO_buf_base,fp-> _IO_buf_base,fp-> _IO_buf_base);
fp-> _IO_write_base = fp-> _IO_write_ptr = fp-> _IO_buf_base;
fp-> _IO_write_end =(fp-> _mode< = 0
&(fp-> _flags&(_IO_LINE_BUF + _IO_UNBUFFERED))
?fp-> ; _IO_buf_base:fp-> _IO_buf_end);
return count;很遗憾,我们再次陷入... _IO_SYSWRITE code>正在做工作:

  / *'syswrite'钩子用于从现有缓冲区
到外部文件。它概括了Unix write(2)函数。
它匹配streambuf :: sys_write虚函数,这是特定于此实现的
。 * /
typedef _IO_ssize_t(* _IO_write_t)(_IO_FILE *,const void *,_IO_ssize_t);
#define _IO_SYSWRITE(FP,DATA,LEN)JUMP2(__write,FP,DATA,LEN)
#define _IO_WSYSWRITE(FP,DATA,LEN)WJUMP2 $ b

所以在do_write里面我们调用文件指针上的write方法。我们从上面的跳转表中知道映射到_IO_new_file_write,那么是什么?

  _IO_ssize_t 
_IO_new_file_write ,data,n)
_IO_FILE * f;
const void * data;
_IO_ssize_t n;
{
_IO_ssize_t to_do = n;
while(to_do> 0)
{
_IO_ssize_t count =(__builtin_expect(f-> _flags2
& _IO_FLAGS2_NOTCANCEL,0)
?write_not_cancel(f- > _fileno,data,to_do)
:write(f-> _fileno,data,to_do));
if(count< 0)
{
f-> _flags | = _IO_ERR_SEEN;
break;
}
to_do - = count;
data =(void *)((char *)data + count);
}
n - = to_do;
if(f-> _offset> = 0)
f-> _offset + = n;
return n;
}

现在它只是调用write!那么在哪里实现呢?你会发现写在 libc / posix / unistd.h

  / *将BUF的N个字节写入FD。返回写入的数字,或-1。 

此函数是取消点,因此未标记
__THROW。 * /
extern ssize_t write(int __fd,__const void * __ buf,size_t __n)__wur;

(注意: __ wur __ attribute__((__warn_unused_result __)))



从表格生成的函数



这只是一个写的原型。在GNU标准库中找不到Linux的write.c文件。相反,你会发现平台特定的方法以各种方式连接到OS写入函数,所有在libc / sysdeps /目录中。



我们将继续以及Linux如何做。有一个名为 sysdeps / unix / syscalls.list 的文件,用于自动生成写入函数。表中的相关数据是:

 文件名:write 
调用者: - (即不适用)
Syscall name:write
Args:Ci:ibn
强名:__libc_write
弱名称:__write,写

不是所有的神秘,除了 Ci:ibn 。 C意味着可取消。冒号将返回类型与参数类型分隔开,如果您想要更深入地解释它们的含义,您可以在shell脚本中看到生成代码的注释, libc / sysdeps / unix / make -syscalls.sh



现在我们希望能够链接到一个名为__libc_write的函数,该函数是由这个shell脚本生成的。但是生成了什么?一些C代码通过一个名为SYS_ify的宏实现写入,你会在sysdeps / unix / sysdep.h中找到

 定义SYS_ify(syscall_name)__NR _ ## syscall_name 

Ah,good old token-pasting:所以基本上,这个 __ libc_write 的实现只不过是一个具有名为 __ NR_write 的参数的syscall函数的代理调用, ,其他参数。



人行道终点...



我知道这是一个迷人的旅程,但现在我们在GNU libc的结尾。该数字 __ NR_write 由Linux定义。对于32位X86架构,它将让您到 linux / arch / x86 / include / asm / unistd_32.h

  #define __NR_write 4 

看看,那么,是syscall的实现。我可能在某个时候做,但现在我只是指出一些参考如何向Linux添加系统调用


I always thought that functions like printf() are, in the last step, defined using inline assembly. That deep in the bowels of stdio.h is buried some asm code that actually tells CPU what to do. For example, in dos, I remember it was implemented by first moving the beginning of the string to some memory location or register and than calling an intterupt.

However, since the x64 version of Visual Studio doesn't support inline assembler at all, it made me wonder how there could be no assembler-defined functions at all in C/C++. How does a library function like printf() get implemented in C/C++ without using assembler code? What actually executes the right software interrupt? Thanks.

解决方案

You're of course right that the rubber has to meet the road at some point. But there's a lot of layers to go through before you can find that place! It sounds like you have some preconceptions based on the DOS days, and that's not too relevant anymore.

There've been some good general points made here, but no one has linked to the precise devils in the details of the source. So in order to make you sufficiently sorry that you asked :) I did a thorough trace of the printf story for GNU's libc and Linux..trying not to hand-wave about any of the steps. In the process I brought some of my own knowledge up to date (WARNING: It's not for the easily bored!):

(The original link is http://blog.hostilefork.com/where-printf-rubber-meets-road/, and it will be maintained there. But to prevent link rotting here is the content cached.)

First Steps

We’ll of course start with the prototype for printf, which is defined in the file libc/libio/stdio.h

extern int printf (__const char *__restrict __format, ...);

You won’t find the source code for a function called printf, however. Instead, in the file /libc/stdio-common/printf.c you’ll find a little bit of code associated with a function called __printf:

int __printf (const char *format, ...)
{
    va_list arg;
    int done;

    va_start (arg, format);
    done = vfprintf (stdout, format, arg);
    va_end (arg);

    return done;
}

A macro in the same file sets up an association so that this function is defined as an alias for the non-underscored printf:

ldbl_strong_alias (__printf, printf);

It makes sense that printf would be a thin layer that calls vfprintf with stdout. Indeed, the meat of the formatting work is done in vfprintf, which you’ll find in libc/stdio-common/vfprintf.c. It’s quite a lengthy function, but you can see that it’s still all in C!

Deeper Down the Rabbit Hole…

vfprintf mysteriously calls outchar and outstring, which are weird macros defined in the same file:

#define outchar(Ch) \
   do \
   { \
       register const INT_T outc = (Ch); \
       if (PUTC (outc, s) == EOF || done == INT_MAX) \
       { \
            done = -1; \
            goto all_done; \
       } \
       ++done; \
   } \
   while (0)

Sidestepping the question of why it’s so weird, we see that it’s dependent on the enigmatic PUTC, also in the same file:

#define PUTC(C, F) IO_putwc_unlocked (C, F)

When you get to the definition of IO_putwc_unlocked in libc/libio/libio.h, you might start thinking that you no longer care how printf works:

#define _IO_putwc_unlocked(_wch, _fp) \
   (_IO_BE ((_fp)->_wide_data->_IO_write_ptr \
        >= (_fp)->_wide_data->_IO_write_end, 0) \
        ? __woverflow (_fp, _wch) \
        : (_IO_wint_t) (*(_fp)->_wide_data->_IO_write_ptr++ = (_wch)))

But despite being a little hard to read, it’s just doing buffered output. If there’s enough room in the file pointer’s buffer, then it will just stick the character into it… but if not, it calls __woverflow. Since the only option when you’ve run out of buffer is to flush to the screen (or whatever device your file pointer represents), we can hope to find the magic incantation there.

Vtables in C?

If you guessed that we’re going to hop through another frustrating level of indirection, you’d be right. Look in libc/libio/wgenops.c and you’ll find the definition of __woverflow:

wint_t 
__woverflow (f, wch)
    _IO_FILE *f;
    wint_t wch;
{
    if (f->_mode == 0)
        _IO_fwide (f, 1);
    return _IO_OVERFLOW (f, wch);
}

Basically, file pointers are implemented in the GNU standard library as objects. They have data members but also function members which you can call with variations of the JUMP macro. In the file libc/libio/libioP.h you’ll find a little documentation of this technique:

/* THE JUMPTABLE FUNCTIONS.

 * The _IO_FILE type is used to implement the FILE type in GNU libc,
 * as well as the streambuf class in GNU iostreams for C++.
 * These are all the same, just used differently.
 * An _IO_FILE (or FILE) object is allows followed by a pointer to
 * a jump table (of pointers to functions).  The pointer is accessed
 * with the _IO_JUMPS macro.  The jump table has a eccentric format,
 * so as to be compatible with the layout of a C++ virtual function table.
 * (as implemented by g++).  When a pointer to a streambuf object is
 * coerced to an (_IO_FILE*), then _IO_JUMPS on the result just
 * happens to point to the virtual function table of the streambuf.
 * Thus the _IO_JUMPS function table used for C stdio/libio does
 * double duty as the virtual function table for C++ streambuf.
 *
 * The entries in the _IO_JUMPS function table (and hence also the
 * virtual functions of a streambuf) are described below.
 * The first parameter of each function entry is the _IO_FILE/streambuf
 * object being acted on (i.e. the 'this' parameter).
 */

So when we find IO_OVERFLOW in libc/libio/genops.c, we find it’s a macro which calls a "1-parameter" __overflow method on the file pointer:

#define IO_OVERFLOW(FP, CH) JUMP1 (__overflow, FP, CH)

The jump tables for the various file pointer types are in libc/libio/fileops.c

const struct _IO_jump_t _IO_file_jumps =
{
  JUMP_INIT_DUMMY,
  JUMP_INIT(finish, INTUSE(_IO_file_finish)),
  JUMP_INIT(overflow, INTUSE(_IO_file_overflow)),
  JUMP_INIT(underflow, INTUSE(_IO_file_underflow)),
  JUMP_INIT(uflow, INTUSE(_IO_default_uflow)),
  JUMP_INIT(pbackfail, INTUSE(_IO_default_pbackfail)),
  JUMP_INIT(xsputn, INTUSE(_IO_file_xsputn)),
  JUMP_INIT(xsgetn, INTUSE(_IO_file_xsgetn)),
  JUMP_INIT(seekoff, _IO_new_file_seekoff),
  JUMP_INIT(seekpos, _IO_default_seekpos),
  JUMP_INIT(setbuf, _IO_new_file_setbuf),
  JUMP_INIT(sync, _IO_new_file_sync),
  JUMP_INIT(doallocate, INTUSE(_IO_file_doallocate)),
  JUMP_INIT(read, INTUSE(_IO_file_read)),
  JUMP_INIT(write, _IO_new_file_write),
  JUMP_INIT(seek, INTUSE(_IO_file_seek)),
  JUMP_INIT(close, INTUSE(_IO_file_close)),
  JUMP_INIT(stat, INTUSE(_IO_file_stat)),
  JUMP_INIT(showmanyc, _IO_default_showmanyc),
  JUMP_INIT(imbue, _IO_default_imbue)
};
libc_hidden_data_def (_IO_file_jumps)

There’s also a #define which equates_IO_new_file_overflow with _IO_file_overflow, and the former is defined in the same source file. (Note: INTUSE is just a macro which marks functions that are for internal use, it doesn’t mean anything like "this function uses an interrupt")

Are we there yet?!

The source code for _IO_new_file_overflow does a bunch more buffer manipulation, but it does call _IO_do_flush:

#define _IO_do_flush(_f) \
    INTUSE(_IO_do_write)(_f, (_f)->_IO_write_base, \
        (_f)->_IO_write_ptr-(_f)->_IO_write_base)

We’re now at a point where _IO_do_write is probably where the rubber actually meets the road: an unbuffered, actual, direct write to an I/O device. At least we can hope! It is mapped by a macro to _IO_new_do_write and we have this:

static
_IO_size_t
new_do_write (fp, data, to_do)
     _IO_FILE *fp;
     const char *data;
     _IO_size_t to_do;
{
  _IO_size_t count;
  if (fp->_flags & _IO_IS_APPENDING)
    /* On a system without a proper O_APPEND implementation,
       you would need to sys_seek(0, SEEK_END) here, but is
       is not needed nor desirable for Unix- or Posix-like systems.
       Instead, just indicate that offset (before and after) is
       unpredictable. */
    fp->_offset = _IO_pos_BAD;
  else if (fp->_IO_read_end != fp->_IO_write_base)
    {
      _IO_off64_t new_pos
    = _IO_SYSSEEK (fp, fp->_IO_write_base - fp->_IO_read_end, 1);
      if (new_pos == _IO_pos_BAD)
    return 0;
      fp->_offset = new_pos;
    }
  count = _IO_SYSWRITE (fp, data, to_do);
  if (fp->_cur_column && count)
    fp->_cur_column = INTUSE(_IO_adjust_column) (fp->_cur_column - 1, data,
                         count) + 1;
  _IO_setg (fp, fp->_IO_buf_base, fp->_IO_buf_base, fp->_IO_buf_base);
  fp->_IO_write_base = fp->_IO_write_ptr = fp->_IO_buf_base;
  fp->_IO_write_end = (fp->_mode <= 0
               && (fp->_flags & (_IO_LINE_BUF+_IO_UNBUFFERED))
               ? fp->_IO_buf_base : fp->_IO_buf_end);
  return count;
}

Sadly we’re stuck again… _IO_SYSWRITE is doing the work:

/* The 'syswrite' hook is used to write data from an existing buffer
   to an external file.  It generalizes the Unix write(2) function.
   It matches the streambuf::sys_write virtual function, which is
   specific to this implementation. */
typedef _IO_ssize_t (*_IO_write_t) (_IO_FILE *, const void *, _IO_ssize_t);
#define _IO_SYSWRITE(FP, DATA, LEN) JUMP2 (__write, FP, DATA, LEN)
#define _IO_WSYSWRITE(FP, DATA, LEN) WJUMP2 (__write, FP, DATA, LEN)

So inside of the do_write we call the write method on the file pointer. We know from our jump table above that is mapped to _IO_new_file_write, so what’s that do?

_IO_ssize_t
_IO_new_file_write (f, data, n)
     _IO_FILE *f;
     const void *data;
     _IO_ssize_t n;
{
  _IO_ssize_t to_do = n;
  while (to_do > 0)
    {
      _IO_ssize_t count = (__builtin_expect (f->_flags2
                         & _IO_FLAGS2_NOTCANCEL, 0)
               ? write_not_cancel (f->_fileno, data, to_do)
               : write (f->_fileno, data, to_do));
      if (count < 0)
    {
      f->_flags |= _IO_ERR_SEEN;
      break;
        }
      to_do -= count;
      data = (void *) ((char *) data + count);
    }
  n -= to_do;
  if (f->_offset >= 0)
    f->_offset += n;
  return n;
}

Now it just calls write! Well where is the implementation for that? You’ll find write in libc/posix/unistd.h:

/* Write N bytes of BUF to FD.  Return the number written, or -1.

   This function is a cancellation point and therefore not marked with
   __THROW.  */
extern ssize_t write (int __fd, __const void *__buf, size_t __n) __wur;

(Note: __wur is a macro for __attribute__ ((__warn_unused_result__)))

Functions Generated From a Table

That’s only a prototype for write. You won’t find a write.c file for Linux in the GNU standard library. Instead, you’ll find platform-specific methods of connecting to the OS write function in various ways, all in the libc/sysdeps/ directory.

We’ll keep following along with how Linux does it. There is a file called sysdeps/unix/syscalls.list which is used to generate the write function automatically. The relevant data from the table is:

File name: write
Caller: "-" (i.e. Not Applicable)
Syscall name: write
Args: Ci:ibn
Strong name: __libc_write
Weak names: __write, write

Not all that mysterious, except for the Ci:ibn. The C means "cancellable". The colon separates the return type from the argument types, and if you want a deeper explanation of what they mean then you can see the comment in the shell script which generates the code, libc/sysdeps/unix/make-syscalls.sh.

So now we’re expecting to be able to link against a function called __libc_write which is generated by this shell script. But what’s being generated? Some C code which implements write via a macro called SYS_ify, which you’ll find in sysdeps/unix/sysdep.h

#define SYS_ify(syscall_name) __NR_##syscall_name

Ah, good old token-pasting :P. So basically, the implementation of this __libc_write becomes nothing more than a proxy invocation of the syscall function with a parameter named __NR_write, and the other arguments.

Where The Sidewalk Ends…

I know this has been a fascinating journey, but now we’re at the end of GNU libc. That number __NR_write is defined by Linux. For 32-bit X86 architectures it will get you to linux/arch/x86/include/asm/unistd_32.h:

#define __NR_write 4

The only thing left to look at, then, is the implementation of syscall. Which I may do at some point, but for now I’ll just point you over to some references for how to add a system call to Linux.

这篇关于没有程序集的C / C ++函数定义的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆