写在前面
如果您对ARM汇编还一无所知的话请先参考ARM汇编hello world
本篇不会广泛详细的列举各种指令,仍然只讲解最关键的部分,然后使用他们来完成一个汇编程序
关键知识
简单加减乘除
加
add x0, x1, x2
x0 = x1 + x2
减
sub x0, x1, x2
x0 = x1 - x2
乘
mul x1, x1, x0
x1 = x1 × x0,保存结果的低64位
除
sdiv x0, x1, x2
x0 = x1 / x2,保存结果的商
msub x3, x0, x2, x1
x3=x1-(x0 × x2),计算上述除法指令的余数
函数调用和循环
以下面这段计算字符串长度的程序为例
.data
strr: .string "hello~"
strlen:
stp x29,x30,[sp, -16]!
mov x10, 0 // use x10 as counter
strlen_loop: // compare if cur byte is \0
ldrb w9, [x0, x10]
cmp w9, 0
beq strlen_lend
add x10, x10, 1
b strlen_loop // enter next round loop
strlen_lend:
mov x0, x10
ldp x29,x30,[sp], 16
ret
_start:
ldr x0, =strr
bl strlen
(上述代码片段并不完整,仅做说明使用)
x29 帧指针寄存器
x30 下一条指令寄存器
sp 栈指针寄存器
执行前先将x29和x30寄存器保存在sp,sp+7 . sp+8,sp+15的地方,并将sp的值减去一些以用作栈空间保存临时变量
结束的时候再从栈中恢复
x30寄存器保存的是下一条指令的地址,在ret执行的时候要用到
如果只是普通的调用这一个函数那即使不保存也不会出问题,但是如果这个函数内又调用了其他函数,那么这个x30寄存器就会指向错误的地址导致永远陷在这个函数中。(bus error)
函数调用和循环实际上都是跳转类指令
上述代码片段使用了bl、b和beq指令
bl指令:跳转到指定的地址执行下一条指令,并将跳转前的下一条指令地址保存在x30寄存器中,在ret的时候恢复(ret伪指令实际上是bl x30)
b指令:和bl一样但不保存x30,也就是说如果用b指令调用函数那结束的时候跳不回来
beq指令:判断标志位进行跳转的b指令,beq是相等时跳转
子函数调用的时候要记得把x9、x10…这些通用寄存器压栈保存,调用完再恢复
系统调用
ARM汇编hello world
这里已经讲过,本篇不再赘述
GDB调试
GDB调试在汇编中是必不可少的,在汇编调试中需要单步观察每个寄存器值的变化来定位问题
不然运行出问题的时候只能两眼一黑
参考AT&T汇编的GDB调试这一节,用GDB调试的操作都是一样的
栈的使用
用GDB调试查看寄存器和内存里的数据
以示例程序的其中一段为例
可以看到,在prints函数的第一行汇编代码执行前:
x29寄存器的值为0
x30寄存器的值为0x400220
sp寄存器的值为ff…f480
从sp-64到sp的值全部为0
第一行汇编代码的含义:
stp(store pair regirester)保存两个64位寄存器的值
到sp-64处,
!意为执行完后把sp的值减去64
那么我们把这一行执行一下,看看是不是这样呢
可以看到,sp寄存器的值从…f480减去0x40变成了…f440
x29寄存器的值0保存在了sp至sp+7这里
x30寄存器的值0x400220保存在了sp+8至sp+15这里
而sp寄存器的值比原先减去了64,那么从sp+16至sp+64处的这48个字节就是分配的栈空间,可以作为临时变量保存数据,比如下面的例子:
这里我们单步执行了str x0, [sp, #16]这条汇编指令,意为把x0寄存器的值保存到sp+16处
值得注意的是和x86汇编有两点区别,
1、x86汇编把rax寄存器的值挪到rbp寄存器里只需要mov就可以了,但ARM64汇编把寄存器的值挪到内存里需要str(存到内存)、ldr(从内存取出)这两条指令
2、x86汇编是把rsp寄存器的值给减少,比如减个64,然后我们可以用rbp-32、rbp-24来保存数据,但ARM64汇编是直接把sp的值减少,然后使用sp+16、sp+24来保存数据
从上图的调试结果可以看到,x0寄存器的值是0x410230(是个字符串的地址),在执行str x0, [sp, #16]之前,sp+16的值是全0,执行之后,sp+16的值变成了0x410230
这就是栈空间的使用
示例代码
实现类似c语言中printf函数的功能
用4个子函数实现
strlen:计算字符串长度
numlen:计算数字长度
numtos:将8位长度以内的数字转成ASCII码的字符串格式
prints:实现了能在格式化字符串中拼接打印一个正数的功能
root@arco-arm:/home/arco/as/prints# cat prints.s
.data
strr: .string "ret=%d\n"
.text
.global strlen
.global numlen
.global numtos
.global prints
.global _start
// p1: source string
// ret: length of string
strlen:
stp x29,x30,[sp, -16]!
mov x10, 0 // use x10 as counter
strlen_loop: // compare if cur byte is \0
ldrb w9, [x0, x10]
cmp w9, 0
beq strlen_lend
add x10, x10, 1
b strlen_loop // enter next round loop
strlen_lend:
mov x0, x10
ldp x29,x30,[sp], 16
ret
// p1: source number
// p2: 10/16
// ret: length of number
numlen:
stp x29,x30,[sp, -16]!
mov x12, 0 // use x12 store length
numlen_loop:
sdiv x9, x0, x1 // use x9 cache quotient
msub x10, x9, x1, x0 // (x10=x0-(x9*x1))use x10 cache remainder
add x11, x9, x10
cmp x11, 0
beq numlen_lend
add x12, x12, 1 // length+1
mov x0, x9 // use quotient as new dividend
b numlen_loop // enter next round loop
numlen_lend:
mov x0, x12 // copy the value from x12 to x0 as result
ldp x29,x30,[sp], 16
ret
// p1: source number
// p2: 10/16
// ret: string format number
numtos:
stp x29,x30,[sp, -32]! // save stack pointer
str x0, [sp, 16] // save origin num in stack
bl numlen // call numlen
mov x11, x0 // use x11 store num length
ldr x0, [sp, 16] // restore origin num from stack
mov x12, 0 // use x12 as counter
add x13, x11, 15 // use x13 cache stack position(sp+16~16+len)
numtos_loop:
sdiv x9, x0, x1 // (x9=x0/x1)use x9 cache quotient
msub x10, x9, x1, x0 // (x10=x0-(x9*x1))use x10 cache remainder
add x10, x10, 0x30 // add '0'
// sp+ :
// sp+17:2
// sp+16:1
strb w10, [sp, x13] // store 1 byte to stack
sub x13, x13, 1 // next byte position(from stack high to low, num l2h
add x12, x12, 1 // counter++
cmp x11, x12 // judge if calculate over
mov x0, x9 // use quotient as dividend
beq numtos_lend
b numtos_loop
numtos_lend:
ldr x0, [sp, 16] // copy stack value to x0
ldp x29,x30,[sp],32 // restore stack
ret
// p1: format string
// p2: target number
prints:
stp x29,x30,[sp,-64]!
mov x9, 0 // use x9 as counter
str x0, [sp, 16] // save format string addr
str x1, [sp, 24] // save target number
prints_loop:
ldr x0, [sp, 16] // restore format string addr
ldrb w10, [x0, x9] // read 1 byte
cmp w10, 0 // judge if over
beq prints_lend
cmp w10, 0x25 // judge if %
beq prints_value
prints_value_over:
// counter point to next char
ldr x1, [sp, 16] // restore str addr
add x1, x1, x9 // next char addr is origin+offset
mov x0, 1
mov x2, 1
mov x8, 64
svc #0
add x9, x9, 1
b prints_loop
prints_lend:
ldp x29,x30,[sp], 64
ret
prints_value:
add x9, x9, 1 // counter+1, jump %
ldrb w10, [x0, x9] // read the byte after %
cmp x10, 0x64 // case %d
beq prints_dec
b prints_value_over // if no matches, jump back and print next char
prints_dec:
str x9, [sp, 40] // save x9 (counter
ldr x0, [sp, 24] // restore target number
mov x1, 10
bl numlen
mov x2, x0
str x2, [sp, 32] // save the length of target number
ldr x0, [sp, 24]
mov x1, 10
bl numtos
ldr x9, [sp, 40] // restore x9
str x0, [sp, 24] // store str format num in sp+24
mov x0, 1 // stdout
mov x1, sp
add x1, x1, 24 // the address of str format num
ldr x2, [sp, 32] // out length
mov x8, 64
svc #0
add x9, x9, 1 // to next char
b prints_value_over
_start:
ldr x0, =strr
mov x1, 123
bl prints
# exit
mov x8, #93
// use return of numlen as return
svc #0
NEXT
下一篇
反编译C语言程序,并学习GNU汇编器的规范操作
包括函数调用、结构体操作、指针使用
ARM汇编[2] 反编译分析汇编代码