ARM汇编[1] 打印格式化字符串(printf

本文介绍了ARM汇编的关键概念,如基本算术运算、函数调用与循环结构,以及如何使用GDB进行调试。同时,通过示例展示了字符串长度计算和类似C语言printf函数的实现。后续内容将涉及C代码反编译与GNU汇编器的应用。
摘要由CSDN通过智能技术生成

写在前面

如果您对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] 反编译分析汇编代码

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值