如何从汇编中调用C函数,以及如何静态链接它?
我在四处玩耍,试图了解计算机和程序的低级操作。为此,我正在尝试将Assembly和C链接起来 我有两个程序文件: “callee.C”中的一些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
$ 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
:编译为64位x86-64又称AMD64体系结构。这些是默认值;您可以使用-march=x86-64-mtune=generic-m64
为自己的系统进行优化-march=native
:编译以独立的C环境为目标。告诉编译器不能假定-ffreestanding
或strlen
或其他库函数可用,因此不要在调用memcpy
、strlen
或memcpy
时优化循环、结构副本或数组初始化。如果您确实提供了gcc可能想要发明调用的任何函数的asm实现,那么您可以忽略这一点。(尤其是当您编写的程序将在操作系统下运行时)memset
:不要链接标准C库或其启动文件。(实际上,-nostlib-nostartfiles
已经“包括”-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>