????????在汇编语言中进行修改并观察不同的执行结果是理解汇编指令和程序行为之间关系的一种有效方式。让我们以一个简单的例子来演示这一点。
目录
????????假设我们有以下C++代码:
#include <iostream>
int main()
{
int t = 1;
std::cout<<t<<std::endl;
return 0;
}
???????代码输出结果:
????????ubuntu 22.04 g++11
?? ? ? ?转换位汇编:
g++ -S c.c
??
.file "c.c"
.text
.local _ZStL8__ioinit
.comm _ZStL8__ioinit,1,1
.globl main
.type main, @function
main:
.LFB1731:
.cfi_startproc
endbr64
pushq %rbp
.cfi_def_cfa_offset 16
.cfi_offset 6, -16
movq %rsp, %rbp
.cfi_def_cfa_register 6
subq $16, %rsp
movl $1, -4(%rbp)
movl -4(%rbp), %eax
movl %eax, %esi
leaq _ZSt4cout(%rip), %rax
movq %rax, %rdi
call _ZNSolsEi@PLT
movq _ZSt4endlIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_@GOTPCREL(%rip), %rdx
movq %rdx, %rsi
movq %rax, %rdi
call _ZNSolsEPFRSoS_E@PLT
movl $0, %eax
leave
.cfi_def_cfa 7, 8
ret
.cfi_endproc
.LFE1731:
.size main, .-main
.type _Z41__static_initialization_and_destruction_0ii, @function
_Z41__static_initialization_and_destruction_0ii:
.LFB2231:
.cfi_startproc
endbr64
pushq %rbp
.cfi_def_cfa_offset 16
.cfi_offset 6, -16
movq %rsp, %rbp
.cfi_def_cfa_register 6
subq $16, %rsp
movl %edi, -4(%rbp)
movl %esi, -8(%rbp)
cmpl $1, -4(%rbp)
jne .L5
cmpl $65535, -8(%rbp)
jne .L5
leaq _ZStL8__ioinit(%rip), %rax
movq %rax, %rdi
call _ZNSt8ios_base4InitC1Ev@PLT
leaq __dso_handle(%rip), %rax
movq %rax, %rdx
leaq _ZStL8__ioinit(%rip), %rax
movq %rax, %rsi
movq _ZNSt8ios_base4InitD1Ev@GOTPCREL(%rip), %rax
movq %rax, %rdi
call __cxa_atexit@PLT
.L5:
nop
leave
.cfi_def_cfa 7, 8
ret
.cfi_endproc
.LFE2231:
.size _Z41__static_initialization_and_destruction_0ii, .-_Z41__static_initialization_and_destruction_0ii
.type _GLOBAL__sub_I_main, @function
_GLOBAL__sub_I_main:
.LFB2232:
.cfi_startproc
endbr64
pushq %rbp
.cfi_def_cfa_offset 16
.cfi_offset 6, -16
movq %rsp, %rbp
.cfi_def_cfa_register 6
movl $65535, %esi
movl $1, %edi
call _Z41__static_initialization_and_destruction_0ii
popq %rbp
.cfi_def_cfa 7, 8
ret
.cfi_endproc
.LFE2232:
.size _GLOBAL__sub_I_main, .-_GLOBAL__sub_I_main
.section .init_array,"aw"
.align 8
.quad _GLOBAL__sub_I_main
.hidden __dso_handle
.ident "GCC: (Ubuntu 11.4.0-1ubuntu1~22.04) 11.4.0"
.section .note.GNU-stack,"",@progbits
.section .note.gnu.property,"a"
.align 8
.long 1f - 0f
.long 4f - 1f
.long 5
0:
.string "GNU"
1:
.align 8
.long 0xc0000002
.long 3f - 2f
2:
.long 0x3
3:
.align 8
4:
? ? ? ? 核心代码:
main:
.LFB1731:
.cfi_startproc
endbr64
pushq %rbp
.cfi_def_cfa_offset 16
.cfi_offset 6, -16
movq %rsp, %rbp
.cfi_def_cfa_register 6
subq $16, %rsp
movl $1, -4(%rbp)
movl -4(%rbp), %eax
movl %eax, %esi
leaq _ZSt4cout(%rip), %rax
movq %rax, %rdi
call _ZNSolsEi@PLT
movq _ZSt4endlIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_@GOTPCREL(%rip), %rdx
movq %rdx, %rsi
movq %rax, %rdi
call _ZNSolsEPFRSoS_E@PLT
movl $0, %eax
leave
.cfi_def_cfa 7, 8
ret
.cfi_endproc
????????我们需要改这块:
?movl $1, -4(%rbp)
????????并修改为:
movl $2, -4(%rbp)
指令格式
movl
: 这是一个汇编指令,代表“move long”。它用于将数据从一个位置移动到另一个位置。这里的“long”意味着它操作的是32位的值(在x86架构中)。操作数
$1
或 $2
: 这是一个立即数操作数。$
表示随后的数字是一个立即数,即直接提供的值,而不是一个地址或寄存器中的值。在第一个指令中,这个值是1
,在修改后的指令中,这个值变成了2
。
-4(%rbp)
: 这是一个内存地址操作数,用于指定目标内存位置。它使用基址寄存器加偏移量的形式。
%rbp
: 基址寄存器(Base Pointer),在函数调用中常用于定位栈帧的基础位置。在大多数情况下,它指向函数栈帧的开始。-4
: 这是一个偏移量,表示从%rbp
指向的地址向下(向较小的地址)移动4个字节。在这个上下文中,它通常用于定位局部变量。操作解释
movl $1, -4(%rbp)
的作用是将值1
存储到栈上由%rbp-4
指定的位置。在C++代码中,这对应于int t = 1;
这一行,即初始化整型变量t
为1
。movl $2, -4(%rbp)
,其作用是将值2
存储到相同的位置。这相当于将C++代码中的t
变量初始化为2
结果
????????这个更改导致程序在执行时,局部变量t
的初始值变为2
,而不是原来的1
。因此,当程序打印这个变量的值时,输出将是2
而不是1
。
? ? ? ? 命令:
g++ c.s -o main
? ? ? ? ?执行:
./main
????????输出结果:?
????????首先,您编写了一个简单的C++程序,其中定义了一个整型变量t
并初始化为1。然后,使用g++
编译器将这段C++代码编译成汇编代码。这一步骤是通过g++ -S
命令完成的,它生成了一个汇编语言文件(通常以.s
为扩展名)。
????????接下来,您打开了生成的汇编文件,并找到了对应于变量t
初始化的那部分代码。在汇编代码中,t
被初始化为1的指令是movl $1, -4(%rbp)
。您将这条指令修改为movl $2, -4(%rbp)
,即改变了t
的初始值从1为2。
????????修改汇编代码后,您使用g++
将修改后的汇编文件编译成一个可执行文件。这是通过类似g++ main.s -o main
的命令完成的。
????????最后,您运行了生成的可执行文件。由于汇编代码中t
的值被改为了2,程序的输出相应地变为了2,而不是原始C++代码中的1。
????????这个过程展示了从高级语言到低级汇编语言的转换,以及如何通过修改汇编代码来改变程序的行为。这是理解计算机体系结构和操作系统底层工作原理的一个实用例子。同时,它也揭示了编译器如何将高级代码转换为机器能够执行的指令。