我有一个简短的hello world程序:
#include <stdio.h> static const char* msg = "Hello world"; int main(){ printf("%s\n", msg); return 0; }
我使用gcc将其编译为以下汇编代码:
.file "hello_world.c" .section .rodata .LC0: .string "Hello world" .data .align 4 .type msg, @object .size msg, 4 msg: .long .LC0 .text .globl main .type main, @function main: .LFB0: .cfi_startproc pushl %ebp .cfi_def_cfa_offset 8 .cfi_offset 5, -8 movl %esp, %ebp .cfi_def_cfa_register 5 andl $-16, %esp subl $16, %esp movl msg, %eax movl %eax, (%esp) call puts movl $0, %eax leave .cfi_restore 5 .cfi_def_cfa 4, 4 ret .cfi_endproc .LFE0: .size main, .-main .ident "GCC: (Ubuntu 4.8.4-2ubuntu1~14.04.3) 4.8.4" .section .note.GNU-stack,"",@progbits
我的问题是:如果我要用汇编语言编写该程序(而不是用C编写然后编译为汇编语言),此代码的所有部分是否必不可少?我了解组装说明,但是有些部分我不理解。例如,我不知道什么是.cfi *,而且我想知道是否需要包括此文件才能在程序集中编写该程序。
可以在这个平台上正常工作的绝对最低要求是
.globl main main: pushl $.LC0 call puts addl $4, %esp xorl %eax, %eax ret .LC0: .string "Hello world"
但这违反了许多ABI要求。符合ABI计划的最低要求是
.globl main .type main, @function main: subl $24, %esp pushl $.LC0 call puts xorl %eax, %eax addl $28, %esp ret .size main, .-main .section .rodata .LC0: .string "Hello world"
目标文件中的其他所有内容是编译器未尽可能严格地优化代码,或者是要写入目标文件的 可选 注释。
这些.cfi_*指令尤其是可选的注释。当且仅当函数可能在引发C ++异常时位于调用堆栈上时,才有 必要 使用它们,但是它们在您可能要从中提取堆栈跟踪信息的任何程序中都很 有用 。如果您打算用汇编语言手工编写非平凡的代码,那么值得学习如何编写它们。不幸的是,它们的文献很少。我目前找不到我认为值得链接的任何内容。
.cfi_*
线
.section .note.GNU-stack,"",@progbits
了解您是否手工编写汇编语言也很重要;它是另一个可选的注释,但是却是一个有价值的注释,因为它的意思是“此目标文件中的任何内容都不需要堆栈是可执行的”。如果程序中的所有目标文件都具有此批注,则内核将使堆栈无法执行,从而在某种程度上提高了安全性。
(为了表明您 确实 需要堆栈是可执行的,请放置"x"而不是""。如果您使用其“嵌套功能”扩展名,则GCC可以这样做。(不要这样做。)
"x"
""
可能值得一提的是,在GCC和GNU binutils使用的“ AT&T”汇编语法(默认情况下)中,存在三种类型的行:在其上带有单个标记并以冒号结尾的行是标签。(我不记得在标签中可以显示哪些字符的规则。) 第一条 标记以点开头但 不 以冒号结尾的行是对汇编程序的某种指令。其他都是汇编指令。