我已经隔离了一个事实,无论我如何努力,都无法将其链接到putchar。
即使是两行,就像
mov r0,$48
bl putchar
当我期望它输出ASCII 0时,它将始终出现段错误
我可以分支到putchar,它可以工作,但是我不能分支链接。含义
mov r0,$48
b putchar
可以工作
我觉得我缺少了一些难以置信的基本知识,但我不知道为什么。我只能认为这与putchar的收益有关,但我不知道是什么。
很抱歉,这似乎是一个愚蠢的问题,但是老实说,我找不到关于此的资源。
编辑:尽管以上陈述对于我自己的独立程序都是正确的,但最终我将在一个子例程中实现它,我认为这可能很重要
答案 0 :(得分:1)
这很难说,因为您没有提供足够的代码,但是您可能会缺少符合以下要求的代码:
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调用约定保留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>
...
答案 1 :(得分:0)
让编译器指导您...编译器并不完美,但是如果我们假定已调试,则它们的输出有效。
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}互通,不是原来的拇指,后来是。就像r4高于r3一样,这里只是用于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不能解决这些问题,它们只会生成错误的代码,如果您不注意的话,即使是较新的gnu也会生成错误的代码。取决于你那天有多幸运。因此,使用bl指令时要非常小心。
我假设我们都相信问题是您没有读过bl修改lr的意思,这意味着如果您想从使用bl的函数中返回,则需要保存该返回地址而不要销毁它。