1.kill4界面介绍
1.1窗口恢复
若不小心关掉左侧项目列表,可以在window中恢复。
1.2Debug调试界面
编写好程序后可以进入Debug界面
左侧会显示寄存器状态
单步执行,会跟随函数逻辑跳转到对应行执行,比如函数调用会跳转到相应行数1执行。
单步执行,但是遇到函数调用会直接将调用的函数执行完毕拿到执行结果,跳转到函数调用的下一行代码执行。
2.汇编语言
2.1汇编语言框架
area reset, code, readonly
code32
entry
end
area reset, code, readonly:这是一个汇编指令,用于定义一个名为“reset”的代码区域,该区域是只读的。通常用于嵌入式系统或启动代码中,表示复位向量或初始化代码。
code32:指定接下来的代码是 32 位 ARM 指令(而不是 Thumb 指令)。
entry:表示程序的入口点(类似于主函数或启动点)。
end:代码结束。
2.2mov赋值操作
area reset, code, readonly
code32
entry
; 赋值操作示例
mov r0, #4 ; 将立即数 4 装入寄存器 r0 (r0 = 4)
mov r1, r0 ; 将寄存器 r0 的值复制到 r1 (r1 = r0 = 4)
mov r2, r1, lsl #1 ; 将 r1 的值逻辑左移 1 位后存入 r2 (r2 = r1 << 1 = 4 << 1 = 8)
mov r3, r1, lsr #1 ; 将 r1 的值逻辑右移 1 位后存入 r3 (r3 = r1 >> 1 = 4 >> 1 = 2)
ror r4, r3, #5 ; 将 r3 的值循环右移 5 位,结果存入 r4
end
看看运行结果
注意:右移会将移出去的数字清除,左侧补0,而循环右移会将移出去的数字补充到左侧。
2.3add加法操作
area reset, code, readonly
code32
entry
; ADD 指令应用示例
mov r0, #100 ; r0 = 100
mov r2, #100 ; r2 = 100
; 立即数加法
add r1, r0, #200 ; r1 = r0 + 200 = 100 + 200 = 300
; 寄存器加法
add r3, r2, r0 ; r3 = r2 + r0 = 100 + 100 = 200
; 带移位的加法(您的注释有误)
add r4, r2, r0, lsl #1 ; r4 = r2 + (r0 << 1) = 100 + (100 × 2) = 100 + 200 = 300
; 注意:lsl #1 是左移1位(乘以2),不是右移
; 立即数表达式加法
add r4, r2, #(100 >> 1) ; r4 = r2 + (100 >> 1) = 100 + 50 = 150
; 汇编器会在编译时计算 100 >> 1 = 50
; 带寄存器控制移位的加法(存在潜在问题)
add r5, r2, r0, lsl r1 ; r5 = r2 + (r0 << r1) = 100 + (100 << 300)
; 注意:r1=300,左移300位没有意义,可能导致意外结果
end
看看运行结果
2.4sub减法操作
area reset, code, readonly
code32
entry
; SUB 指令应用示例
mov r0, #100 ; r0 = 100
mov r2, #100 ; r2 = 100
; 立即数减法
sub r1, r0, #50 ; r1 = 100 - 50 = 50
; 寄存器减法
sub r3, r2, r0 ; r3 = 100 - 100 = 0
; 带左移的减法(乘以2)
sub r4, r2, r0, lsl #1 ; r4 = 100 - (100 × 2) = -100
; 带右移的减法(除以2)
sub r4, r2, r0, lsr #1 ; r4 = 100 - (100 ÷ 2) = 50
; 合理的移位寄存器操作
mov r6, #2 ; 设置合理的移位量
sub r5, r2, r0, lsl r6 ; r5 = 100 - (100 × 4) = -300
; 使用反向减法指令(当需要交换操作数顺序时)
rsb r7, r0, r2 ; r7 = r2 - r0 = 100 - 100 = 0
rsb r8, r0, #200 ; r8 = 200 - r0 = 200 - 100 = 100
end
看看运行结果
2.5ldr伪指令,加载任意32位数
加载任意32位数:
area reset, code, readonly
code32
entry
; LDR 指令应用示例
mov r1, #10 ; r1 = 10
mov r0, #0xffffffff ; 错误!ARM的mov 不能加载32位立即数
mvn r0, #0xffffffff ; 正确!使用mvn取反加载,r0 = 0x00000000
ldr r0, =0xfac0 ; 正确!使用ldr伪指令加载32位立即数
end
看看运行结果
2.6bic指定位清零
area reset, code, readonly
code32
entry
; BIC(位清除)指令应用示例
mov r0, #0xffffffff ; r0 = 0xFFFFFFFF (所有位都为1)
mov r1, #1 ; r1 = 1 (二进制: 0001)
; 方法1:使用立即数直接清除特定位
bic r0, r0, #4 ; 清除第2位(从0开始计数)
; #4 = 二进制 0100 (第2位为1)
; r0 = r0 AND NOT(0100) = 0xFFFFFFFB
; 方法2:使用寄存器移位清除特定位
bic r0, r0, r1, lsl #2 ; r1 = 1 (0001), 左移2位 = 0100 (4)
; 清除第2位,结果同上
; 方法3:使用表达式清除特定位
bic r0, r0, #(1 << 2) ; 1左移2位 = 0100 (4)
; 清除第2位,结果同上
end
运行结果
2.7orr指定位置1
area reset, code, readonly
code32
entry
; ORR(按位或)指令应用示例 - 指定位置1
mov r0, #0x0 ; r0 = 0x00000000 (所有位都为0)
; 使用ORR指令将第10位置1(位编号从0开始)
orr r0, r0, #(1 << 10) ; r0 = r0 OR 0x00000400 = 0x00000400
; 1 << 10 = 21? = 1024 = 0x400
; 第10位(二进制第11位)被设置为1
end
运行结果
2.8subs带标志位的减法
area reset, code, readonly
code32
entry
end
运行结果
2.9比较大小
比较两位数大小:
area reset, code, readonly
code32
entry
;比较两数大小
;mov r0, #0x100 ; R0 = 0x100 (十进制256)
;mov r1, #0x2 ; R1 = 0x2 (十进制2)
;cmp r0, r1 ; 比较 R0 和 R1 的值
;movge r2, r0 ; 如果 R0 >= R1 (Greater than or Equal),则 R2 = R0
;movlt r2, r1 ; 如果 R0 < R1 (Less Than),则 R2 = R1
end
运行结果
比较三位数大小:
area reset, code, readonly
code32
entry
;比较三个数大小
mov r0, #0xff ;R0 = 255
mov r1, #0x100 ;R1 = 256
mov r2, #0xfe ;R2 = 254
cmp r0, r1 ;比较r0和r1
movge r3, r0 ;如果 R0 >= R1,则 R2 = R0
movlt r3, r1 ;如果 R0 < R1 (Less Than),则 R2 = R1
cmp r3, r2 ;比较r3和r2
movge r4, r3 ;如果 R3 >= R2,则 R4 = R3
movlt r4, r2 ;如果 R3 < R2 (Less Than),则 R4 = R2
end
运行结果
2.10用分支结构比较大小
area reset, code, readonly
code32
entry
;分支结构判断两个数大小
mov r0, #0x100
mov r1, #0x2
cmp r0, r1 ;比较r0,r1
bge greatr ;r0>r1跳转到greatr
blt less ;r0<r1跳转到less
greatr
mov r2, r0
b finish
less
mov r2, r1
b finish
finish
b finish ;无限循环
end
运行结果
2.11循环结构求1~99的和
while
area reset, code, readonly
code32
entry
;循环
;1到99的和 (while)
mov r0, #0x0 ; R0 = 0 (用于存储和)
mov r1, #0x1 ; R1 = 1 (计数器,从1开始)
mov r2, #0x64 ; R2 = 100 (循环终止条件)
loop ; 循环开始
cmp r1, r2 ; 比较计数器和100
bge finish ; 如果计数器 >= 100,跳转到结束
add r0, r0, r1 ; 累加:和 = 和 + 计数器
add r1, r1, #1 ; 计数器加1
b loop ; 继续循环
finish
b finish ; 无限循环
end
运行结果
结果正确
do while
area reset, code, readonly
code32
entry
;循环
;1到99的和 (do while)
mov r0, #0x0
mov r1, #0x1
mov r2, #0x64
loop
add r0, r0, r1
add r1, r1, #1
cmp r1, r2
blt loop
finish
b finish
end
运行结果与上述一致
2.12函数封装并调用
area reset, code, readonly
code32
entry
;函数封装并调用
b main ;跳转到主程序,避免执行函数代码
asm_maxTwoNum
mov r0, #100
mov r1, #200
cmp r0, r1
movge r3, r0
movlt r3, r1
mov pc, lr
main
mov r0, #10
mov r1, #20
bl asm_maxTwoNum ;调用函数:bl会跳转到函数并将返回地址保存到lr寄存器
mov r2, #10
mov r3, #20
finish
b finish
end
通过b跳转可以实现函数的封装和调用。
2.13STMDB(STMFD)解决函数调用嵌套,lr保存地址不够用
在多级函数调用时,lr会被覆盖,因此我们需要用stmfd将lr的内容存入栈中,在执行完函数后用ldmfd弹出,这样就能保证函数准确的返回。
操作流程:先递减栈指针,然后存储寄存器到内存,更新栈指针
注意,在每次函数调用时,都要加上压栈和弹栈操作!
area reset, code, readonly
code32
entry
;函数封装并调用
b main ;跳转到主程序,避免执行函数代码
asm_fun0
mov r0, #100
mov r1, #200
bx lr
asm_maxTwoNum
mov r0, #100
mov r1, #200
cmp r0, r1
stmfd sp!, {r0-r12,lr};带!栈帧移动,不带不移动,将r0-r12,lr压栈
bl asm_fun0 ;调用函数:bl会跳转到函数并将返回地址保存到lr寄存器
ldmfd sp!, {r0-r12,lr};与压栈列表对应,否则会产生错位,将r0-r12,lr弹出
movge r3, r0
movlt r3, r1
bx lr ;返回lr寄存器地址
main
ldr sp, =0x40001000
mov r0, #10
mov r1, #20
stmfd sp!, {r0-r12,lr};带!栈帧移动,不带不移动,将r0-r12,lr压栈
bl asm_maxTwoNum ;调用函数:bl会跳转到函数并将返回地址保存到lr寄存器
ldmfd sp!, {r0-r12,lr};与压栈列表对应,否则会产生错位,将r0-r12,lr弹出
mov r2, #10
mov r3, #20
finish
b finish
end
我们可以看到,程序可以进行准确的跳转
3.概念补充
3.1ARM内核工作模式以及切换
7种模式:
1. 异常触发模式切换
; 1. 复位(Reset) - 进入 Supervisor 模式
; 处理器上电或复位时自动进入
; 这是程序的起点
; 2. 未定义指令异常 - 进入 Undefined 模式
udiv r0, r1, r2 ; 如果处理器不支持UDIV指令
; 自动切换到Undefined模式
; 3. 软件中断(SWI)或SVC - 进入 Supervisor 模式
svc 0x1234 ; 主动触发管理模式切换
; 用于系统调用
; 4. 预取指中止 - 进入 Abort 模式
; 当处理器尝试执行无效地址的指令时
; 5. 数据中止 - 进入 Abort 模式
ldr r0, [r1] ; 如果r1指向无效内存地址
; 自动切换到Abort模式
; 6. IRQ中断 - 进入 IRQ 模式
; 外部设备触发普通中断时
; 7. FIQ中断 - 进入 FIQ 模式
; 高速外设触发快速中断时
2. 手动模式切换
; 通过修改CPSR(当前程序状态寄存器)的模式位
mrs r0, cpsr ; 读取CPSR到r0
bic r0, r0, #0x1F ; 清除模式位(低5位)
orr r0, r0, #0x13 ; 设置为Supervisor模式(0b10011)
msr cpsr_c, r0 ; 写回CPSR,切换模式
; 注意:在用户模式下不能直接修改CPSR,会触发异常
3.各种模式的具体切换场景
1. User → Supervisor
系统调用:应用程序通过SVC指令请求操作系统服务
复位:处理器启动时自动进入
软件调试:调试器需要更高权限时
2. User → IRQ/FIQ
硬件中断:外设(键盘、定时器、网络等)产生中断
实时事件:需要立即处理的紧急事件
3. User → Abort
内存访问错误:访问非法内存地址或权限不足
页错误:虚拟内存管理中页面未加载或只读写入
4. User → Undefined
执行未定义指令:处理器遇到不认识的指令
协处理器访问:访问不存在的协处理器
5. 任何模式 → 任何模式
异常嵌套:在处理一个异常时又发生另一个异常
模式切换指令:操作系统有意识地切换模式
3.2立即数判断
立即数 是指在指令中直接编码的常数数值,而不是来自寄存器或内存的值。
在 ARM 架构中,由于指令长度固定为 32 位,不能将完整的 32 位立即数直接编码在指令中。因此,ARM 使用了一种巧妙的编码方案。
ARM 中的 12 位立即数实际上由两部分组成:
8 位 常数(0-255)
4 位 旋转值(0-15)
编码格式:
[11:8] - 旋转值 (rotate)
[7:0] - 8位常数 (imm8)
实际数值计算:
真正的 32 位立即数 = imm8
循环右移 (2 × rotate
) 位
mov r0, #100 ; #100 就是立即数
add r1, r2, #0xFF ; #0xFF 就是立即数
cmp r3, #42 ; #42 就是立即数
一个 32 位数值是合法立即数的条件是:它可以通过 8 位常数循环右移偶数位得到。
判断方法:
将数值表示为 32 位二进制
检查是否能被分解为 8 位有效字段
这个 8 位字段必须能通过循环右移偶数位得到
; 合法立即数示例:
mov r0, #0xFF ; 合法:0xFF = 0xFF ROR 0
mov r0, #0xFF00 ; 合法:0xFF00 = 0xFF ROR 24 (2×12)
mov r0, #0x3FC0 ; 合法:0x3FC0 = 0xFF ROR 2 (2×1)
; 非法立即数示例:
mov r0, #0x12345 ; 非法:无法用8位常数通过循环右移得到
mov r0, #0x101 ; 非法:0x101 = 0000 0001 0000 0001
; 无法找到8位的连续有效位
3.3b,bl,bx指令
1. b 指令(Branch - 无条件跳转)
b label ; 跳转到标签处
b #0x1000 ; 跳转到绝对地址 0x1000
特点:
最简单的跳转指令,不保存返回地址,用于循环、条件分支、无限跳转。
2. bl 指令(Branch with Link - 带链接的分支)
main:
mov r0, #10
mov r1, #20
bl add_numbers ; 调用函数,lr = main+4的地址
b end_program
add_numbers:
add r2, r0, r1 ; r2 = r0 + r1
mov pc, lr ; 返回到调用处(使用保存的lr)
特点:
跳转前将返回地址(下一条指令地址)保存到 lr 寄存器,用于函数调用,是最常用的函数调用指令。
3. bx 指令(Branch and eXchange - 分支并交换)
; 函数返回
bl some_function
; ... 其他代码 ...
some_function:
; 函数体
bx lr ; 返回到调用处(lr保存的地址)
; 切换指令集
mov r0, #0x1001 ; 地址最低位为1,表示Thumb模式
bx r0 ; 跳转到0x1000,进入Thumb模式
特点:
跳转到寄存器指定的地址(不是立即数或标签),可以根据目标地址的最低位切换 ARM/Thumb 指令集,常用于函数返回和模式切换
3.4ARM内核采用的栈
ARM 内核采用的栈类型不是固定的,而是可配置的,但最常用的是满递减栈。
这是 ARM 架构的默认和最常用的栈类型。
特点:满:栈指针指向最后一个入栈的数据。
递减:栈向内存低地址方向增长。