如何从汇编中调用C函数,以及如何静态链接它?

如何从汇编中调用C函数,以及如何静态链接它?,c,linux,assembly,gcc,x86-64,C,Linux,Assembly,Gcc,X86 64,我在四处玩耍,试图了解计算机和程序的低级操作。为此,我正在尝试将Assembly和C链接起来 我有两个程序文件: “callee.C”中的一些C代码: 我可以像这样构建和执行程序: $ as ./caller.asm -o ./caller.obj $ gcc -c ./callee.c -o ./callee.obj $ ld -e my_entry_pt -lc ./callee.obj ./caller.obj -o ./prog.out -dynamic-linker /lib64/ld

我在四处玩耍,试图了解计算机和程序的低级操作。为此,我正在尝试将Assembly和C链接起来

我有两个程序文件:

“callee.C”中的一些C代码:

我可以像这样构建和执行程序:

$ as ./caller.asm -o ./caller.obj
$ gcc -c ./callee.c -o ./callee.obj
$ ld -e my_entry_pt -lc ./callee.obj ./caller.obj -o ./prog.out -dynamic-linker /lib64/ld-linux-x86-64.so.2
$ ldd ./prog.out
    linux-vdso.so.1 (0x00007fffdb8fe000)
    libc.so.6 => /lib64/libc.so.6 (0x00007f46c7756000)
    /lib64/ld-linux-x86-64.so.2 (0x00007f46c7942000)
$ ./prog.out
Hello, World!
一路上,我遇到了一些问题。如果未设置-dynamic linker选项,则默认为:

$ ld -e my_entry_pt -lc ./callee.obj ./caller.obj -o ./prog.out
$ ldd ./prog.out
    linux-vdso.so.1 (0x00007ffc771c5000)
    libc.so.6 => /lib64/libc.so.6 (0x00007f8f2abe2000)
    /lib/ld64.so.1 => /lib64/ld-linux-x86-64.so.2 (0x00007f8f2adce000)
$ ./prog.out
bash: ./prog.out: No such file or directory
为什么会这样?我的系统上的链接器默认值有问题吗?我该如何修理它

而且,静态链接不起作用

$ ld -static -e my_entry_pt -lc ./callee.obj ./caller.obj -o ./prog.out
ld: ./callee.obj: in function `my_c_func':
callee.c:(.text+0x16): undefined reference to `write'
为什么会这样?write()不应该只是系统调用“write”的c库包装器吗?我怎样才能修好它

我在哪里可以找到关于C函数调用约定的文档,这样我就可以了解参数是如何来回传递的,等等


最后,虽然这对这个简单的例子似乎有效,但我在初始化C堆栈时是否做错了什么?我是说,现在我什么都没做。在开始调用函数之前,我应该从内核为堆栈分配内存、设置边界并设置%rsp和%rbp。还是内核加载器为我处理了所有这些?如果是这样的话,Linux内核下的所有架构都会为我解决吗?

虽然Linux内核提供了一个名为
write
的系统调用,但这并不意味着您会自动获得一个包装函数,它的名称与您可以从C调用的名称相同,即
write()
。事实上,如果不使用libc,则需要内联程序集从C调用任何系统调用,因为libc定义了这些包装函数

与其显式地将二进制文件链接到
ld
,不如让
gcc
为您做这件事。如果源代码以
.s
后缀结尾,它甚至可以组装程序集文件(在内部执行适当版本的
作为
)。看起来您的链接问题只是GCC假设与您自己如何通过LD实现之间的分歧

不,它不是虫子;
ld
ld的默认路径。因此
不是现代x86-64 GNU/Linux系统上使用的路径。(
/lib/ld64.so.1
可能在multi-arch系统能够同时支持i386和x86-64版本的库的尘埃落定之前就已经在早期的x86-64 GNU/Linux端口上使用了。现代系统使用
/lib64/ld-Linux-x86-64.so.2

Linux使用。(PDF)描述了初始执行环境(调用
\u start
时)和调用约定。实际上,您有一个初始化的堆栈,其中存储了环境和命令行参数


让我们构造一个完整的工作示例,包含C和汇编(AT&T语法)源代码,以及最终的静态和动态二进制文件

首先,我们需要一个
Makefile
来保存键入的长命令:

#SPDX许可证标识符:CC0-1.0
CC:=gcc
CFLAGS:=-Wall-Wextra-O2-march=x86-64-mtune=generic-m64\
-ffreserving-nostlib-nostartfiles
LDFLAGS:=
全部:静态程序动态程序
清洁:
rm-f静态程序动态程序*.o
%.o:%.c
$(CC)$(CFLAGS)$^-c-o$@
%.o:%.s
$(CC)$(CFLAGS)$^-c-o$@
动态程序:main.o asm.o
$(CC)$(CFLAGS)$^$(LDFLAGS)-o$@
静态程序:main.o asm.o
$(CC)-静态$(CFLAGS)$^$(LDFLAGS)-o$@
Makefiles特别注重缩进,但会将制表符转换为空格。因此,粘贴完上面的内容后,运行
sed-e的|^*|\t |'-i Makefile
将缩进修复回选项卡

上述Makefile和所有以下文件中的SPDX许可证标识符告诉您,这些文件是根据以下文件获得许可的:也就是说,这些文件都是专用于公共域的

使用的编译标志:

  • -Wall-Wextra
    :启用所有警告。这是一个很好的做法

  • -O2
    :优化代码。这是一个常用的优化级别,通常被认为是足够的,不太极端

  • -march=x86-64-mtune=generic-m64
    :编译为64位x86-64又称AMD64体系结构。这些是默认值;您可以使用
    -march=native
    为自己的系统进行优化

  • -ffreestanding
    :编译以独立的C环境为目标。告诉编译器不能假定
    strlen
    memcpy
    或其他库函数可用,因此不要在调用
    strlen
    memcpy
    memset
    时优化循环、结构副本或数组初始化。如果您确实提供了gcc可能想要发明调用的任何函数的asm实现,那么您可以忽略这一点。(尤其是当您编写的程序将在操作系统下运行时)

  • -nostlib-nostartfiles
    :不要链接标准C库或其启动文件。(实际上,
    -nostlib
    已经“包括”
    -nostartfiles
    ,因此仅
    -nostlib
    就足够了。)

接下来,让我们创建一个头文件,
nolib.h
,它实现了
nolib\u exit()
nolib\u write()
组退出和写入系统调用的包装器:

//SPDX许可证标识符:CC0-1.0
/*在x86-64上需要Linux*/
#如果!已定义(uuu linux_uuuu)| |!已定义(\uuuux86\u64\uuuuuuu)
#错误“这仅适用于x86-64上的Linux。”
#恩迪夫
/*已知系统调用号,不依赖于glibc或内核头*/
#定义系统写入1
#定义系统退出组231
//通常你会用
//#包括)

接下来,我们需要一些C代码<代码>main.c

//SPDX许可证标识符:CC0-1.0
#包括“nolib.h”
常量字符*c_函数(void)
{
返回“C函数”;
}
静态内联长nolib_put(const char*msg)
{
如果(!msg){
返回nolib_write(1,“(null)”,6);
}否则{
const char*end=msg;
while(*结束)
$ ld -e my_entry_pt -lc ./callee.obj ./caller.obj -o ./prog.out
$ ldd ./prog.out
    linux-vdso.so.1 (0x00007ffc771c5000)
    libc.so.6 => /lib64/libc.so.6 (0x00007f8f2abe2000)
    /lib/ld64.so.1 => /lib64/ld-linux-x86-64.so.2 (0x00007f8f2adce000)
$ ./prog.out
bash: ./prog.out: No such file or directory
$ ld -static -e my_entry_pt -lc ./callee.obj ./caller.obj -o ./prog.out
ld: ./callee.obj: in function `my_c_func':
callee.c:(.text+0x16): undefined reference to `write'
reset ; make clean all
asm_function(0) returns 'C function', and asm_function(1) returns 'One'.
strip --strip-unneeded static-prog dynamic-prog
Dump of assembler code for function write:
=> 0x0000000000401040 <+0>:     endbr64                 # for CET, or NOP on CPUs without CET
   0x0000000000401044 <+4>:     mov    %fs:0x18,%eax    ### this faults with no TLS setup
   0x000000000040104c <+12>:    test   %eax,%eax
   0x000000000040104e <+14>:    jne    0x401060 <write+32>
   0x0000000000401050 <+16>:    mov    $0x1,%eax        # simple case: EAX = __NR_write
   0x0000000000401055 <+21>:    syscall 
   0x0000000000401057 <+23>:    cmp    $0xfffffffffffff000,%rax
   0x000000000040105d <+29>:    ja     0x4010b0 <write+112>        # update errno on error
   0x000000000040105f <+31>:    retq                               # else return

   0x0000000000401060 <+32>:    sub    $0x28,%rsp               # the non-simple case:
   0x0000000000401064 <+36>:    mov    %rdx,0x18(%rsp)          # write is an async cancellation point or something
   0x0000000000401069 <+41>:    mov    %rsi,0x10(%rsp)
   0x000000000040106e <+46>:    mov    %edi,0x8(%rsp)
   0x0000000000401072 <+50>:    callq  0x4010e0 <__libc_enable_asynccancel>
   0x0000000000401077 <+55>:    mov    0x18(%rsp),%rdx
   0x000000000040107c <+60>:    mov    0x10(%rsp),%rsi
   0x0000000000401081 <+65>:    mov    %eax,%r8d
   0x0000000000401084 <+68>:    mov    0x8(%rsp),%edi
   0x0000000000401088 <+72>:    mov    $0x1,%eax
   0x000000000040108d <+77>:    syscall 
   0x000000000040108f <+79>:    cmp    $0xfffffffffffff000,%rax
   0x0000000000401095 <+85>:    ja     0x4010c4 <write+132>
   0x0000000000401097 <+87>:    mov    %r8d,%edi
   0x000000000040109a <+90>:    mov    %rax,0x8(%rsp)
   0x000000000040109f <+95>:    callq  0x401140 <__libc_disable_asynccancel>
   0x00000000004010a4 <+100>:   mov    0x8(%rsp),%rax
   0x00000000004010a9 <+105>:   add    $0x28,%rsp
   0x00000000004010ad <+109>:   retq   
   0x00000000004010ae <+110>:   xchg   %ax,%ax

   0x00000000004010b0 <+112>:   mov    $0xfffffffffffffffc,%rdx   # errno update for the simple case
   0x00000000004010b7 <+119>:   neg    %eax
   0x00000000004010b9 <+121>:   mov    %eax,%fs:(%rdx)          # thread-local errno?
   0x00000000004010bc <+124>:   mov    $0xffffffffffffffff,%rax
   0x00000000004010c3 <+131>:   retq

   0x00000000004010c4 <+132>:   mov    $0xfffffffffffffffc,%rdx   # same for the async case
   0x00000000004010cb <+139>:   neg    %eax
   0x00000000004010cd <+141>:   mov    %eax,%fs:(%rdx)
   0x00000000004010d0 <+144>:   mov    $0xffffffffffffffff,%rax
   0x00000000004010d7 <+151>:   jmp    0x401097 <write+87>