我已经分离了这样一个事实:无论我怎么努力,我都无法将链接分支到putchar。
甚至连两条线就像
mov r0,$48
bl putchar
当我期望它打印ASCII 0时,总是会出现段错误
我可以分支到putchar,它会工作,但我不能分支链接。含义
mov r0,$48
b putchar
将工作
我觉得我错过了一些非常基本的东西,但我无法弄明白为什么。我只能假设它与putchar的返回有关,但我不知道是什么。
对不起,如果这看起来像一个愚蠢的问题,但老实说我找不到这方面的资源。
编辑:虽然上面的陈述对于我来说甚至是一个独立的程序也是如此,但我最终在子程序中实现了这一点,我认为这可能很重要
这很难说,因为您没有提供足够的代码,但您可能缺少符合ARM calling conventions所需的代码。 完整的代码应该保存堆栈上的fp,lr,而不是调用putchar,然后恢复fp,lr并返回或恢复fp,pc,这基本相同。
使用以下内容创建名为example.s的文件:
.arch armv7-a
.align 2
.globl main
.arch armv7-a
.syntax unified
.arm
main:
push {fp, lr}
mov r0, #48
bl putchar
pop {fp, pc}
编译并链接它 - 我编译了一个静态版本,因为我测试了qemu-arm:
/opt/arm/gcc-arm-8.3-2019.03-x86_64-arm-linux-gnueabihf/bin/arm-linux-gnueabihf-gcc -static -O0 -o example example.s
执行它 - 在我的情况下使用qemu-arm
/opt/qemu-3.1.0-static/bin/qemu-arm example
0
请注意:
pop {fp, pc}
相当于:
pop {fp, lr}
ret
我希望这有帮助。
更新
putchar()确实返回传递的字符或r0中的EOF。由于r0未在main中修改,因此它包含的值将返回给被调用者,即bash,并且可以使用echo $?
命令查看:
opt/qemu-3.1.0/bin/qemu-arm example
0
echo $?
48
根据ARM calling conventions的第15页,r4-r8在子程序调用中被保留,但是r0-r3可能不是。
使用objdump反汇编示例程序:
/opt/arm/gcc-arm-8.3-2019.03-x86_64-arm-linux-gnueabihf/bin/arm-linux-gnueabihf-objdump -D example > example.lst
在example.lst中,您可以看到putchar()是: 1)根据ARM Calling Convention保留r4,r5,r6,r7,r8,lr, 2)利用您提到的已修改的寄存器:
00016f50 <putchar>:
16f50: e92d41f0 push {r4, r5, r6, r7, r8, lr}
16f54: e30354a8 movw r5, #13480 ; 0x34a8
16f58: e3405008 movt r5, #8
16f5c: e1a06000 mov r6, r0
16f60: e5954000 ldr r4, [r5]
16f64: e5943000 ldr r3, [r4]
16f68: e3130902 tst r3, #32768 ; 0x8000
16f6c: 1a000015 bne 16fc8 <putchar+0x78>
16f70: e5943048 ldr r3, [r4, #72] ; 0x48
16f74: ee1d7f70 mrc 15, 0, r7, cr13, cr0, {3}
16f78: e2477d13 sub r7, r7, #1216 ; 0x4c0
16f7c: e5932008 ldr r2, [r3, #8]
16f80: e1520007 cmp r2, r7
16f84: 0a000030 beq 1704c <putchar+0xfc>
16f88: e3a02001 mov r2, #1
16f8c: e1931f9f ldrex r1, [r3]
16f90: e3510000 cmp r1, #0
16f94: 1a000003 bne 16fa8 <putchar+0x58>
16f98: e1830f92 strex r0, r2, [r3]
16f9c: e3500000 cmp r0, #0
16fa0: 1afffff9 bne 16f8c <putchar+0x3c>
16fa4: f57ff05b dmb ish
16fa8: 1a00002d bne 17064 <putchar+0x114>
16fac: e5943048 ldr r3, [r4, #72] ; 0x48
16fb0: e5950000 ldr r0, [r5]
16fb4: e5837008 str r7, [r3, #8]
16fb8: e5932004 ldr r2, [r3, #4]
16fbc: e2822001 add r2, r2, #1
16fc0: e5832004 str r2, [r3, #4]
16fc4: ea000000 b 16fcc <putchar+0x7c>
16fc8: e1a00004 mov r0, r4
16fcc: e5903014 ldr r3, [r0, #20]
16fd0: e6efc076 uxtb ip, r6
16fd4: e5902018 ldr r2, [r0, #24]
16fd8: e1530002 cmp r3, r2
16fdc: 32832001 addcc r2, r3, #1
16fe0: 35802014 strcc r2, [r0, #20]
16fe4: 35c36000 strbcc r6, [r3]
16fe8: 2a000019 bcs 17054 <putchar+0x104>
16fec: e5943000 ldr r3, [r4]
16ff0: e3130902 tst r3, #32768 ; 0x8000
16ff4: 1a000005 bne 17010 <putchar+0xc0>
16ff8: e5940048 ldr r0, [r4, #72] ; 0x48
16ffc: e5903004 ldr r3, [r0, #4]
17000: e2433001 sub r3, r3, #1
17004: e5803004 str r3, [r0, #4]
17008: e3530000 cmp r3, #0
1700c: 0a000001 beq 17018 <putchar+0xc8>
17010: e1a0000c mov r0, ip
17014: e8bd81f0 pop {r4, r5, r6, r7, r8, pc}
17018: e5803008 str r3, [r0, #8]
1701c: f57ff05b dmb ish
17020: e1902f9f ldrex r2, [r0]
17024: e1801f93 strex r1, r3, [r0]
17028: e3510000 cmp r1, #0
1702c: 1afffffb bne 17020 <putchar+0xd0>
17030: e3520001 cmp r2, #1
17034: dafffff5 ble 17010 <putchar+0xc0>
17038: e3a01081 mov r1, #129 ; 0x81
1703c: e3a02001 mov r2, #1
17040: e3a070f0 mov r7, #240 ; 0xf0
17044: ef000000 svc 0x00000000
17048: eafffff0 b 17010 <putchar+0xc0>
1704c: e1a00004 mov r0, r4
17050: eaffffd8 b 16fb8 <putchar+0x68>
...
让编译器指导你...编译器并不完美,但如果我们假设已调试,那么它们的输出就可以了。
void next ( char );
void fun ( void )
{
next(0x33);
}
00000000 <fun>:
0: e92d4010 push {r4, lr}
4: e3a00033 mov r0, #51 ; 0x33
8: ebfffffe bl 0 <next>
c: e8bd4010 pop {r4, lr}
10: e12fff1e bx lr
没有链接,但表明需要保留lr。 R4用于保持堆栈在64位边界上对齐,具体取决于编译器的版本,它可能在此规则之前,并且只会推送lr并弹出它。
最大兼容性,最老的拇指指令,armv4t到armv7a和armv8m的工作。
00000000 <fun>:
0: b510 push {r4, lr}
2: 2033 movs r0, #51 ; 0x33
4: f7ff fffe bl 0 <next>
8: bc10 pop {r4}
a: bc01 pop {r0}
c: 4700 bx r0
(确实符合堆栈对齐btw)。
你发现你可以在那里分支:
00000000 <fun>:
0: 2033 movs r0, #51 ; 0x33
2: f7ff bffe b.w 0 <next>
但在这种情况下尾部优化。如果你想分支链接并返回这个不适合你。
int next ( char );
int fun ( char a )
{
return(next(a)+1);
}
00000000 <fun>:
0: b508 push {r3, lr}
2: f7ff fffe bl 0 <next>
6: 3001 adds r0, #1
8: bd08 pop {r3, pc}
在某些时候pop支持与pop {pc}非原创拇指互通,但后来是。像r3上面的r4这里只是用于64位堆栈对齐,许多较低的寄存器在这里工作,它是一种无关紧要的东西。
我们需要了解架构的另一个原因是你是否可以/应该使用bl,因为bl在模式之间不起作用,但是如果你的架构支持blx的话。
00001000 <fun>:
1000: b508 push {r3, lr}
1002: f000 e804 blx 100c <next>
1006: 3001 adds r0, #1
1008: bd08 pop {r3, pc}
100a: bf00 nop
0000100c <next>:
100c: e2800002 add r0, r0, #2
1010: e12fff1e bx lr
00001000 <fun>:
1000: e92d4010 push {r4, lr}
1004: eb000003 bl 1018 <__next_from_arm>
1008: e8bd4010 pop {r4, lr}
100c: e2800001 add r0, r0, #1
1010: e12fff1e bx lr
00001014 <next>:
1014: 3002 adds r0, #2
1016: 4770 bx lr
00001018 <__next_from_arm>:
1018: e59fc000 ldr ip, [pc] ; 1020 <__next_from_arm+0x8>
101c: e12fff1c bx ip
1020: 00001015 andeq r1, r0, r5, lsl r0
1024: 00000000 andeq r0, r0, r0
在这两种情况下,链接器修复了问题,但旧版本的gnu不会这样做,只是构建错误的代码,如果你不注意,即使是更新的代码也会构建错误的代码。取决于你那天有多幸运。所以使用bl指令要非常小心。
我假设我们都相信问题是你没有读过bl修改lr这意味着如果你想从使用bl的函数返回你需要保存那个返回地址而不是销毁它。