如何从汇编语言过渡到c语言?
从编译过程谈起
编译小知识
源代码编译后得到目标文件 (二进制文件)
不同语言可编译得到相同格式的目标文件
链接器负责将目标文件组装得到可执行文件
老生常谈的问题。。。
c语言中的函数调用是如何进行的?
栈上的秘密
cdecl 调用约定 (C语言默认调用约定)
参数从右向左入栈
函数调用者负责参数的入栈出栈
函数本身根据约定使用栈中参数
gcc编译器使用的栈帧布局
ebp是函数调用以及函数返回的核心寄存器
ebp为当前栈帧的基准 (存储上一个栈帧的ebp值)
通过ebp能够获取返回值地址,参数,局部变量,等
目标文件分析
void g()
{
}
int func(int a, int b)
{
g();
a = a + b;
return a;
}
int main()
{
func(1, 2);
return 0;
}
leave 指令将 ebp 寄存器的值赋给 esp 寄存器中,以释放分配给该过程的所有堆栈空间。然后,它从堆栈中恢复 ebp 寄存器的旧值。
通过反汇编我们观察到,c语言进行函数调用必须经历的4个步骤,将当前的ebp入栈;让ebp指向esp,也就是上一个栈帧的ebp值;函数执行完后,让esp指向ebp,将old ebp 出栈,ebp恢复为上一个栈帧的ebp值;最后返回到函数调用完后的下一条指令执行。
使用汇编语言编写Linux可执行程序
定义 _start 标签作为程序执行的起点
通过 int 0x80 使用内核服务 (执行系统调用)
交互关键字
global
- 从汇编语言中导出符号 (变量或函数)
extern
- 使用外部文件中定义的符号 (变量或函数)
示例分析
混合编程注意事项
相同的目标文件格式 (如:elf 格式)
相同的函数调用约定 (如:cdecl 调用约定)
相同的活动记录 (栈帧) 结构 (如:ebp 基准)
混合编程实战
entry.asm
global _start
global vstr
global vlen
global print
extern c_func
[section .data]
vstr db "D.T.Software", 0x0A
vlen dd $ - vstr
[section .text]
_start:
mov ebp, 0
call c_func
call exit
print:
push ebp
mov ebp, esp
mov edx, [ebp + 12]
mov ecx, [ebp + 8]
mov ebx, 1
mov eax, 4
int 0x80
pop ebp
ret
exit:
mov ebx, 0
mov eax, 1
int 0x80
main.c
extern void print(char*, int len);
extern char vstr[];
extern int vlen;
int c_func()
{
char* delphi = "Delphi\n";
print(vstr, vlen);
return 0;
}
成功打印了输出结果。
nasm -f elf entry.asm -o entry.o
gcc -c mian.c -o main.o
ld -s entry.o main.o -o app.out
通过上述方式生成可执行文件。
global关键字用来让一个符号对链接器可见,可以供其他链接对象模块使用。
global _start 让_start符号成为可见的标示符,这样链接器就知道跳转到程序中的什么地方并开始执行。linux寻找这个 _start标签作为程序的默认进入点。
c程序在调用print函数时,调用者c_func会将参数依次从右往左入栈,然后将返回地址入栈;在print函数里,要遵守函数的调用约定,字符串的起始地址存放在[ebp + 8]处,字符串的长度存放在[ebp + 12] 处。
c语言与汇编语言在满足以下条件时可混合编程
遵循相同的函数调用约定
遵循相同的目标文件格式