参考网站:
https://www.runoob.com/cprogramming/c-tutorial.html 语法
https://godbolt.org/ 对应的x64 msvc v19.36编译结果
https://quick-bench.com/ 性能测试?
https://chat.openai.com/ 对编译结果进行解释
第一段是c语言源码,一般是基本的简单语法
#include
int main()
{
printf("Hello, World! \n");
return 0;
}
第二段是以上代码对应的汇编源码:
_DATA SEGMENT
COMM ?_OptionsStorage@?1??__local_stdio_printf_options@@9@9:QWORD ; `__local_stdio_printf_options'::`2'::_OptionsStorage
_DATA ENDS
_DATA SEGMENT
$SG10424 DB 'Hello, World! ', 0aH, 00H
_DATA ENDS
main PROC
$LN3:
sub rsp, 40 ; 00000028H
lea rcx, OFFSET FLAT:$SG10424
call printf
xor eax, eax
add rsp, 40 ; 00000028H
ret 0
main ENDP
第三段为openai对第二段汇编结果的解释:
_DATA SEGMENT 声明了一个名为_DATA的数据段,这通常用于存放全局和静态数据。
COMM ?_OptionsStorage@?1??__local_stdio_printf_options@@9@9:QWORD ; `__local_stdio_printf_options'::`2'::_OptionsStorage
COMM(通讯段)的声明,表示这是一个公共的或外部的数据项。
这个特定的数据项是QWORD(8字节或64位)大小,和内部的printf实现有关。
_DATA ENDS 数据段_DATA结束。
_DATA SEGMENT
$SG10424 DB 'Hello, World! ', 0aH, 00H
定义了一个名为$SG10424的数据,DB表示字节数据。
换行符0aH和字符串结束标志00H。
_DATA ENDS
main PROC 开始定义main函数。
$LN3: 一个局部标签,用于内部跳转。
sub rsp, 40 ; 00000028H
从栈指针rsp减去40(16进制的28H),为局部变量和可能的函数调用预留空间。
lea rcx, OFFSET FLAT:$SG10424
将$SG10424的地址加载到rcx寄存器。
lea是"load effective address"的缩写,
rcx是在x64调用约定中用于传递第一个参数的寄存器,
所以这里其实是将字符串的地址传递给接下来的printf函数。
call printf 调用printf函数。
xor eax, eax
将eax寄存器清零。在x64调用约定中,函数的返回值是通过eax寄存器返回的,所以这里是设置main函数的返回值为0。
add rsp, 40 ; 00000028H
将之前为局部变量预留的40个字节的栈空间释放。
ret 0 返回,结束main函数。
main ENDP main函数结束定义。
第四段是一点总结:
#include 并没有将未被调用的函数源码引入。
main中没有局部变量,但存在sub rsp, 40 ,原因可能是调用约定预留的、对齐要求、某种编译器优化策略、安全检查需求...这个并不明确。
call printf这一句是怎么知道跳转到哪执行?跳转到一个表中的位置,这个表在程序加载时由动态链接器填写。这个表称为全局偏移表 (GOT) 或程序查找表 (PLT)。
这个条目会进一步查找GOT来找到函数的实际地址。当程序首次调用这个函数时,动态链接器会填写这个地址。随后的函数调用将直接使用这个地址,而不需要再次通过动态链接器。
位置无关代码 (PIC): 这是用于动态链接的代码,可以加载到任意地址并仍然工作。PIC通常使用GOT,因为它允许代码在不知道它将在哪里运行的情况下被编译。
实际也可以静态编译,此处直接是printf函数的已知地址。
动态编译时,就是GOT、PLT机制。
在MASM汇编语言中(Microsoft Macro Assembler),FLAT是一个特殊的段修饰符,它表示“默认平坦内存模型”。x86体系结构的早期版本(如16位的实模式)使用了段偏移地址模式。
修改代码后,比较汇编的变更
printf("Hello, World!\n%d\n",666); //变更点
COMM ?_OptionsStorage@ ... //这一部分汇编没变
$SG10424 DB 'Hello, World!', 0aH, '%d', 0aH, 00H //字符串变更
mov edx, 666 ; 0000029aH //参数2倒着赋值
lea rcx, OFFSET FLAT:$SG10424 //参数1没变
call printf
其它关联信息
在x86-64体系结构中,以下是一些常用的64位寄存器的描述:
RAX/RBX/RCX/RDX: 这些是通用寄存器,它们是32位x86架构中EAX, EBX, ECX, 和 EDX寄存器的64位版本。
RAX (Accumulator Register): 主要用于算术、逻辑和数据传输操作。
RBX (Base Register): 这是一个通用寄存器,但在某些情境下可以作为基地址指针。
RCX (Counter Register): 主要用于“重复”指令和“循环”指令的计数器。
RDX (Data Register): 通常与RAX一起用于乘法和除法操作。
RSI/RDI: 也是通用寄存器,在32位架构中,它们是ESI和EDI。
RSI (Source Index): 在字符串操作中经常用作源地址指针。
RDI (Destination Index): 在字符串操作中经常用作目标地址指针。
RBP/RSP: 这些是堆栈操作的寄存器,在32位架构中,它们是EBP和ESP。
RBP (Base Pointer): 用作堆栈帧的基地址指针。
RSP (Stack Pointer): 用作堆栈的顶部指针,它始终指向堆栈的当前位置。
R8-R15: 这些是x86-64架构新引入的通用寄存器,为了增加更多的寄存器以提高性能。
RIP: 指令指针。它指向下一个要执行的指令。
段寄存器: 如 CS (代码段), DS (数据段), SS (堆栈段), ES, FS, GS。
在实模式下,它们用于访问内存地址。
但在现代操作系统的保护模式和长模式下,它们的用途有所不同,尤其是FS和GS,它们通常用于特定的系统级任务。
每个64位寄存器还有对应的32位、16位和8位的子寄存器。
例如,RAX的32位子寄存器是EAX,16位子寄存器是AX,
而8位的低字节和高字节分别是AL和AH。