书中的代码是32位的,在目前流行的X86_64的Linux版本中很难运行通过,所以有一个想法,把里面的32位汇编代码改成64位的以便方便学习研究。我并不想改变书中的代码风格,毕竟我是为了学习AT&T语法风格才看这本书的。
要编译运行就要有编译环境,原书中没有说明编译环境和使用的Linux版本,我用当下流行的版本运行,我所用的Linux版本:Ubuntu 20.04.3 X86_64 TLS,gcc 版本 9.3.0。
.section .data
output:
.asciz "Now my age is %d\n"
age:
.int 23
# .data ends
.section .text
.global _start
_start:
nop
pushl $output
pushl age
call printf
add $8, %esp
pushl $0
call exit
要改成X86_64的64位汇编代码,并符合AMD64 ABI规范。我分两步走,第一步,提取例程中的算法,第二步,改写例程。
这个例程看起来很简单,它调用了c语言的两个函数printf 和 exit,那么我们在改写成64位汇编代码时要符合AMD64 ABI的规范。为了方便编译,我们使用gcc,这样可以一步到位直接生成可执行文件。为了提取算法,我在这里引入一个虚拟CPU,这个CPU是尺度可变的,它有8个通用寄存器,分别是vAX,vBX,vCX,vDX,vSP,vBP,vSI,vDI,与实体的X86架构CPU中的寄存器对应。
与64位CPU的对应关系:
R0 | R1 | R2 | R3 | R4 | R5 | R6 | R7 |
RAX | RCX | RDX | RBX | RSP | RBP | RSI | RDI |
vAX | vCX | vDX | vBX | vSP | vBP | vSI | vDI |
与32位CPU的对应关系:
R0D | R1D | R2D | R3D | R4D | R5D | R6D | R7D |
EAX | ECX | EDX | EBX | ESP | EBP | ESI | EDI |
vAX | vCX | vDX | vBX | vSP | vBP | vSI | vDI |
有虚拟CPU还不够,还要一个中立的语言来描述,我采用自然语言并模拟BASIC指令来描述。
开始提取例程算法:
;Non-Basic Description for Assembly
statement
label output:
chap string '......'
label age:
int ?
end statement
declare extern function printf, exit
begin
pass parameter for function
call printf
pass parameter for function
call exit
end
汇编语言中的c函数调用要使用寄存器传递参数,传递参数的寄存器是有顺序的, 在64位的X86架构CPU中依次是RDI,RSI,RDX,RCX。现在把上面的算法描述语言改一改。
;Non-Basic Description for Assembly
statement
label output:
chap string '......'
label age:
int ?
end statement
declare extern function printf, exit
begin
let vdi load address of label output
let vsi load content from label age
call printf
let vdi = 0
call exit
end
要把例程改写成64位的汇编代码只要把算法描述语言中的vdi,vsi改成rdi,rsi即可。
2.改写例程
代码如下:
/*-----------------------------------------------------------------------------
At&T style Assembly program, would use:
gcc -no-pie -o P1-test P1-test.S
-------------------------------------------------------------------------------*/
.section .data
output:
.asciz "Now my age is %d\n"
age:
.int 23
# .data ends
.section .text
.global main
.extern printf
.extern exit
main:
nop
pushq $output
popq %rdi
pushq (age)
popq %rsi
xor %rax, %rax
call printf
xor %rdi, %rdi
call exit
# .text ends
# end _start
为了方便编译,我用main作为入口标记,用gcc编译即可。
例如:gcc -no-pie -o P1-test P1-test.S