GCC生成的汇编代码

假设我们写了一个C代码文件 code.c包含下面代码:

int accum = 0;

int sum(int x, int y)
{
    int t = x + y;
    accum += t;
    return t;
}

这是用echo命令输入源码的效果,简单的就是最好的:)
 


一、查看GCC生成的汇编代码
在命令行上用“-S”选项,就能看到C编译器产生的汇编代码:
 
gcc -S code.c         (注意:这里是大写的-S,如果用小写gcc会说找不到main函数)
 
这段汇编代码没有经过优化:
 
.file "code.c"
.globl _accum
 .bss
 .align 4
_accum:
 .space 4
 .text
.globl _sum
 .def _sum; .scl 2; .type 32; .endef
_sum:
 pushl %ebp
 movl %esp, %ebp
 subl $4, %esp                  # 为局部变量t在栈帧上分配空间
 movl 12(%ebp), %eax    # %eax <- y
 addl 8(%ebp), %eax       # %eax <- x + y
 movl %eax, -4(%ebp)     # t <- x +y
 movl -4(%ebp), %eax     # %eax <- t
 addl %eax, _accum        # _accum <- t + _accum
 movl -4(%ebp), %eax     # %eax <- t
 leave                                 # 平衡堆栈: %esp <- %ebp , popl %ebp
 ret                                      
 
 
下面是使用“-O2”选项开启二级优化的效果:
 
gcc -O2 -S code.c
 
 .file "code.c"
.globl _accum
 .bss
 .align 4
_accum:
 .space 4
 .text
 .p2align 4,,15                    # 使下一条指令的地址从16的倍数处开始,
.globl _sum                        # 最多浪费15个字节
 .def _sum; .scl 2; .type 32; .endef
_sum:
 pushl %ebp                       # 保存原%ebp   
 movl %esp, %ebp        
 movl 12(%ebp), %eax     # %eax <- y
 movl 8(%ebp), %edx       # %edx <- x
 popl %ebp                        # 恢复原%ebp 
 addl %edx, %eax             # %eax <- x + y
 addl %eax, _accum         # _accum <- _accum + x + y
 ret
 
 
GCC产生的汇编代码有点难读,它包含一些我们不关心的信息。所有以 "." 开头的行都是指导汇编器和链接器的命令,称为“汇编器命令”。
 
 
代码中已经除去了所有关于局部变量名或数据类型的信息,但我们还是看到了一个对全局变量_accum的引用,这是因为编译器还不能确定这个变量会放在存储中的哪个位置。
二、用GDB查看目标文件的字节表示
 
  首先,我们用反汇编器来确定函数sum的代码长度是19字节。然后我们在文件code.o上运行GNU调试工具GDB,输入命令:
(gdb) x/19xb sum
这条命令告诉GDB检查(简写为"x")19个以十六进制格式表示的字节。
三、反汇编目标文件
 
   在Linux系统中,带 "-d" 命令行选项调用OBJDUMP可以完成这个任务:
  objdump -d code.o
 
从这里可以看出函数sum的代码长度正好是19字节。
四、生成实际可执行的代码
  这需要对一组目标文件运行链接器,而这一组目标代码文件中必须包含有一个Main函数。在 main.c 中有这样的函数:
  int main()
  {
       return sum(1,2);
  }
然后,我们用如下方法生成可执行文件:
gcc -O2 -o prog code.o main.c
再反汇编:
objdump -d prog
00401050 <_sum>:
  401050: 55                              push   %ebp
  401051: 89 e5                        mov    %esp,%ebp
  401053: 8b 45 0c                   mov    0xc(%ebp),%eax
  401056: 8b 55 08                   mov    0x8(%ebp),%edx
  401059: 5d                              pop    %ebp
  40105a: 01 d0                        add    %edx,%eax
  40105c: 01 05 10 20 40 00  add    %eax,0x402010
  401062: c3                              ret 
  这段代码与code.c反汇编产生的代码几乎完全一样。一个主要的区别是左边列出的地址不同。第二个不同之处在于链接器终于确定了存贮全局变量accum的地址。地址由原来的0x0变成了现在的0x402010
 
 
 
 
相关知识点:
 
 

7.3 .align abs-expr, abs-expr, abs-expr
Pad the location counter (in the current subsection) to a particular storage b
oundary. The first expression (which must be absolute) is the alignment requir
ed, as described below.
填充数据,使得位置计数器向后移动到达一个特殊的存储器边界。第一个表达式(其值必须为立既数)
是必须的,这会在下面描述。
The second expression (also absolute) gives the fill value to be stored in the
padding bytes. It (and the comma) may be omitted. If it is omitted, the paddi
ng bytes are normally zero. However, on some systems, if the section is marked
as containing code and the fill value is omitted, the space is filled with no
-op instructions.
第二个表达式(其值也必须为立既数)给出将被填充的值。此表达式(和逗号)可以省略。如果省略,
则通常用0填充。但是在某些系统中,如果一个段被标记为包含代码并且省略了填充的值,则使用no-op
指令进行填充。
The third expression is also absolute, and is also optional. If it is present,
it is the maximum number of bytes that should be skipped by this alignment di
rective. If doing the alignment would require skipping more bytes than the spe
cified maximum, then the alignment is not done at all. You can omit the fill v
alue (the second argument) entirely by simply using two commas after the requi
red alignment; this can be useful if you want the alignment to be filled with
no-op instructions when appropriate.
第三个表达式的值同样必须为立既数,并且也是可选项。此项表示这个对齐指令可以跳过的
最大的字节数。如果进行对齐操作需要跳过的字节数大于指定的数量,则对齐操作根本不会
被执行。通过在第一个参数后面简单的使用两个逗号,你完全可以省略填充值(第二个参数);
如果你希望对齐操作在适当的时侯用"no-op"指令进行填充,这个方法将非常有用。
The way the required alignment is specified varies from system to system. For
the a29k, hppa, m68k, m88k, w65, sparc, and Hitachi SH, and i386 using ELF for
mat, the first expression is the alignment request in bytes. For example ‘.al
ign 8’ advances the location counter until it is a multiple of 8. If the loca
tion counter is already a multiple of 8, no change is needed.
对于不同的系统,对齐的方式有所不同。a29k, hppa, m68k, m88k, w65, spar和Hitachi以及
使用ELF格式的i386系统,第一个表达式是需要对齐的字节数。例如".align 8"指令增加位置计数器
的值使其为8的倍数,如果已经是8的倍数则无需改变。
For other systems, including the i386 using a.out format, and the arm and stro
ngarm, it is the number of low-order zero bits the location counter must have
after advancement. For example ‘.align 3’ advances the location counter unti 
l it a multiple of 8. If the location counter is already a multiple of 8, no c
hange is needed.
其它某些系统,包括使用a.out格式的i386 以及arm和strongarm,第一个表达式是完成移动后,
位置计数器的值的低位中0的数量。例如,".align 3"指令增加计数器的值到8的倍数(译注:
数值的低3位总是三个0)。
This inconsistency is due to the different behaviors of the various native ass
emblers for these systems which GAS must emulate. GAS also provides .balign an
d .p2align directives, described later, which have a consistent behavior acros
s all architectures (but are specific to GAS).
因为GAS必须模仿这些系统中各种汇编器的不同行为,由此导致了这种差异。GAS 也提供了.balign
和.p2align 指令,这在后面描述,它们在所有的体系结构中拥有一致的行为。
 
7.54 .p2align[wl] abs-expr, abs-expr, abs-expr
Pad the location counter (in the current subsection) to a particular storage b
oundary. The first expression (which must be absolute) is the number of low-or
der zero bits the location counter must have after advancement. For example ‘
.p2align 3’ advances the location counter until it a multiple of 8. If the lo
cation counter is already a multiple of 8, no change is needed.
填充数据,使得位置计数器向后移动到达一个特殊的存储器边界。第一个表达式(其值必须为立既数)
是计数器值的低位中0的数量。例如:".p2align 3"将使得计数器移动到地址为8的倍数的位置,
如果地址已经是8的倍数则无需移动。
The second expression (also absolute) gives the fill value to be stored in the
padding bytes. It (and the comma) may be omitted. If it is omitted, the paddi
ng bytes are normally zero. However, on some systems, if the section is marked
as containing code and the fill value is omitted, the space is filled with no
-op instructions.
第二个表达式(其值也必须为立既数)给出将被填充的值。此表达式(和逗号)可以省略。如果省略,
则通常用0填充。但是在某些系统中,如果一个段被标记为包含代码并且省略了填充的值,则使用no-op
指令进行填充。

The third expression is also absolute, and is also optional. If it is present,
it is the maximum number of bytes that should be skipped by this alignment di
rective. If doing the alignment would require skipping more bytes than the spe
cified maximum, then the alignment is not done at all. You can omit the fill v
alue (the second argument) entirely by simply using two commas after the requi
red alignment; this can be useful if you want the alignment to be filled with
no-op instructions when appropriate.
第三个表达式的值同样必须为立既数,并且也是可选项。此项表示这个对齐指令可以跳过的
最大的字节数。如果进行对齐操作需要跳过的字节数大于指定的数量,则对齐操作根本不会
被执行。通过在第一个参数后面简单的使用两个逗号,你完全可以省略填充值(第二个参数)。
如果你希望对齐操作在适当的时侯用"no-op"指令进行填充,这个方法将非常有用。
The .p2alignw and .p2alignl directives are variants of the .p2align directive.
The .p2alignw directive treats the fill pattern as a two byte word value. The
.p2alignl directives treats the fill pattern as a four byte longword value. F
or example, .p2alignw 2,0x368d will align to a multiple of 4. If it skips two
bytes, they will be filled in with the value 0x368d (the exact placement of th
e bytes depends upon the endianness of the processor). If it skips 1 or 3 byte
s, the fill value is undefined.
.p2alignw 和 .p2alignl是.p2align指令的两种变形。.p2alignw以一个2字节大小的数据进行
填充。.p2alignl指令以一个4字节大小的数据填充。例如:指令.p2alignw 2,0x368d 将地址对
齐到4的倍数。如果此操作跳过了两字节,则这两个字节是以数值0x368d 进行填充的(这些字节的
准确布局依赖于处理器的字节顺序)。如果跳过的字节数是1或3,那么填充的值是未定义的。
 
7.65 .scl class 
Set the storage-class value for a symbol. This directive may only be used insi
de a .def/.endef pair. Storage class may flag whether a symbol is static or ex
ternal, or it may record further symbolic debugging information.
The ‘.scl’ directive is primarily associated with COFF output; when configur
ed to generate b.out output format, as accepts this directive but ignores it.
设置一个符号的存储类型值。这条指令只能被用在一对.def/.endef里面。存储类型可以表明符号是
静态(static)或外部(external)类型,或者可能进一步记录符号的调试信息。
".scl"指令主要是和COFF输出有关,当设置成产生b.out输出格式时,汇编器可以接受这条指令,
但它将被忽略。

7.84 .type int (COFF version)
This directive, permitted only within .def/.endef pairs, records the integer i
nt as the type attribute of a symbol table entry.
‘.type’ is associated only with COFF format output; when as is configured fo
r b.out output, it accepts this directive but ignores it.
这条命令仅允许用在一对.def/.endef之内。记录整数int 作为一个符号表条目的类型属性。
".type"命令仅与COFF输出格式相关,当汇编器被设置成输出 a.out文件时,汇编器可以接受
这条命令,但它将会被忽略。
 
<think>我们被要求回答:如何使用gcc编译生成汇编代码? 根据引用[2]和引用[3]提供的信息,我们可以总结如下: 1. 使用gcc的编译选项可以生成汇编代码,通常使用`-S`选项可以生成汇编代码(注意:这是汇编代码,不是反汇编,但有时我们也把从机器码转换为汇编代码的过程称为反汇编)。 2. 实际上,我们通常使用`objdump`工具来进行反汇编,而gcc本身并不直接生成汇编代码,但我们可以通过gcc编译生成可执行文件或目标文件,然后用objdump反汇编。 但是,根据引用[2]提到: arm-none-eabi-objdump的参数-S表示尽可能的把原来的代码和反汇编出来的代码一起呈现出来,-S参数需要结合 arm-linux-gcc编译参数-g,才能达到反汇编时同时输出原来的代码。 所以,我们可以分两种情况: 情况一:生成汇编代码(编译阶段,从C代码汇编代码) 使用gcc的-S选项,例如:gcc -S source.c -o output.s 这样会生成汇编代码文件output.s。 情况二:生成汇编代码(从可执行文件或目标文件反汇编) 步骤: 1. 用gcc编译源文件生成目标文件或可执行文件,并且为了在反汇编时看到C源代码,需要加上-g选项(调试信息)。 2. 使用objdump工具进行反汇编,命令为:objdump -d 文件名 (只反汇编代码段) 或者使用:objdump -D 文件名 (反汇编所有段) 如果希望混合显示源代码汇编代码,则使用:objdump -S 文件名 注意:使用objdump的-S选项时,需要目标文件或可执行文件包含调试信息(即编译时使用-g选项)。 因此,具体步骤为: 1. 编译C程序并加入调试信息(-g): gcc -g -c source.c -o source.o # 生成目标文件 或者直接生成可执行文件: gcc -g source.c -o program 2. 使用objdump反汇编目标文件或可执行文件: objdump -S source.o > source_disassembly.txt # 将反汇编输出到文件 或 objdump -S program > program_disassembly.txt 另外,如果只想生成汇编代码(即编译过程中的汇编输出,而不是反汇编),则使用: gcc -S source.c # 默认生成source.s gcc -S source.c -o output.s 但注意:-S生成汇编代码是编译器生成汇编,而objdump反汇编的是机器码对应的汇编,两者在格式和注释上可能有所不同。 因此,回答用户问题:如何生成汇编代码?我们应推荐使用objdump工具,并配合编译时使用-g选项以得到更好的可读性(混合源代码汇编)。 总结步骤: 方法一:生成汇编代码(编译阶段) gcc -S source.c -o source.s 方法二:生成汇编代码(从二进制文件反汇编gcc -g source.c -o program objdump -S program > disassembly.txt 注意:如果使用交叉编译工具链,则objdump应替换为对应的交叉版本,如arm-linux-gnueabi-objdump。 根据引用[2]的例子,他们使用的是arm-none-eabi-objdump,所以对于不同的目标平台,需要选择对应的objdump。 因此,在回答中,我们可以这样写:</think>### 如何使用 GCC 生成汇编代码 在 Linux 环境下,使用 GCC 生成汇编代码主要通过两种方式实现,以下是详细步骤和原理说明: --- #### 方法一:直接生成汇编文件(编译阶段) 使用 GCC 的 `-S` 选项可在编译过程中生成汇编代码文件: ```bash # 基本用法 gcc -S source.c -o output.s # 完整示例(包含调试信息) gcc -g -S -fverbose-asm -O0 source.c -o source.s ``` **参数说明**: - `-S`:在编译阶段停止,生成汇编代码文件 - `-g`:添加调试信息(与反汇编工具配合使用) - `-O0`:禁用优化(保持代码结构清晰) - `-fverbose-asm`:在汇编中添加注释和变量名[^3] **生成文件**: - 输出 `.s` 文件包含完整的汇编指令和符号信息 - 示例输出片段: ```nasm main: pushq %rbp movq %rsp, %rbp # 创建栈帧 movl $42, -4(%rbp) # int a = 42; movl -4(%rbp), %eax popq %rbp ret ``` --- #### 方法二:从二进制文件反汇编(链接后) 使用 `objdump` 工具对可执行文件进行反汇编: ```bash # 编译时添加调试信息 gcc -g source.c -o program # 反汇编可执行文件 objdump -dS program > disassembly.txt ``` **关键参数**: - `-d`:反汇编代码段 - `-S`:交叉显示源代码汇编(需 `-g` 编译选项)[^2] - `-M intel`:指定 Intel 汇编语法(默认 AT&T) **输出示例**: ```nasm 0000000000001139 <main>: #include <stdio.h> int main() { 1139: 55 push %rbp 113a: 48 89 e5 mov %rsp,%rbp int a = 42; 113d: c7 45 fc 2a 00 00 00 movl $0x2a,-0x4(%rbp) # 0x2a = 42 return a; 1144: 8b 45 fc mov -0x4(%rbp),%eax } 1147: 5d pop %rbp 1148: c3 ret ``` --- #### 高级技巧 1. **指定目标架构**: ```bash # ARM 平台反汇编 arm-linux-gnueabi-objdump -d -S program > arm_disasm.txt ``` 2. **分离代码段**: ```bash # 仅反汇编 .text 段 objdump -j .text -dS program > code_section.txt ``` 3. **生成 Intel 语法**: ```bash objdump -d -M intel program > intel_disasm.txt ``` --- #### 注意事项 1. **调试信息依赖**: - 交叉显示源码需编译时添加 `-g` 选项[^2] - 优化级别会影响反汇编可读性(建议使用 `-O0`) 2. **工具链选择**: | 文件类型 | 工具 | 特点 | |---------------|--------------|--------------------------| | 可执行文件 | objdump | 支持交叉显示源码 | | 目标文件(.o) | objdump -d | 显示未链接的汇编 | | 动态库(.so) | objdump -T | 查看动态符号表 | 3. **ARM 平台特殊处理**: ```bash # Thumb 指令集反汇编 arm-none-eabi-objdump -d --disassembler-options=force-thumb program ``` > 提示:使用 `gcc -Wa,-adhln -g source.c` 可实时查看编译过程中的汇编输出[^3]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值