确定 return 函数在 ARM Cortex-M 系列上的地址
Determining return address of function on ARM Cortex-M series
我想在 Keil 中确定一个函数的 return 地址。我在 Keil uvision 中以调试模式打开了反汇编部分。显示的是这样的一些汇编代码:
我的目的是通过在微控制器上使用缓冲区溢出向微控制器注入一个简单的二进制代码。see: Buffer overflow
我想确定 "test" 函数的 return 地址。是否必须知道如何阅读汇编代码,或者是否有任何技巧可以找到 return 地址?
我是汇编新手。
R14
或其他名称 LR
持有 return 地址。在左边你可以在图片中看到它。是0x08000287
.
您可以在 Cortex-M 文档中找到所有答案
http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.ddi0337h/Chdedegj.html
调用函数时,R14会被调用("BL"或"BLX")指令后的地址覆盖。如果该函数不调用任何其他函数,R14 将 经常 在其持续时间内保留 return 地址。此外,如果函数尾调用另一个函数,则尾调用可以替换为分支("B" 或 "BX"),R14 保存原始调用者的 return 地址。如果一个函数对另一个函数进行非尾调用,则有必要在此之前的某个时间保存 R14 "somewhere"(通常是堆栈,但可能保存到另一个先前使用的调用者保存的寄存器),并检索稍后从堆栈中获取该值,但如果启用优化,R14 的保存位置通常是不可预测的。
一些编译器可能有一种模式可以一致地堆叠事物以使其足够可用,但代码将非常依赖于编译器。最有可能成功的技术可能是做类似的事情:
extern int getStackAddress(uint8_t **addr); // Always returns zero
void myFunction(...whavever...)
{
uint8_t *returnAddress;
if (getStackAddress(&returnAddress)) return; // Put this first.
}
其中 getStackAddress
将是一个机器代码函数,它将 R14 存储到 R0 中的地址,将 R0 加载为零,然后分支到 R14。可能跟随它的代码序列相对较少,如果代码检查存储在 returnAddress
中的地址处的指令并识别这些代码序列之一,它就会知道 return 地址用于myFunction
存储在适合所讨论序列的位置。例如,如果它看到:
test r0,r0
be ...
pop {r0,pc}
它会知道调用者的地址在堆栈中是第二个。同样,如果它看到:
cmp r0,#0
bne somewhere:
somewhere: ; Compute address based on lower byte of bne
pop {r0,r1,r2,r4,r5,pc}
那么它就知道来电者的地址是第六个。
编译器可以使用一些指令来测试寄存器是否为零,一些编译器可能使用 be
,而其他编译器可能使用 bne
,但对于上面的代码,编译器可能会使用上述模式,因此计算 pop
指令中设置了多少位将揭示堆栈上 return 地址的位置。直到运行时才知道这个测试是否真的有效,但在它声称识别 return 地址的情况下,它实际上应该是正确的。
我想在 Keil 中确定一个函数的 return 地址。我在 Keil uvision 中以调试模式打开了反汇编部分。显示的是这样的一些汇编代码:
我的目的是通过在微控制器上使用缓冲区溢出向微控制器注入一个简单的二进制代码。see: Buffer overflow 我想确定 "test" 函数的 return 地址。是否必须知道如何阅读汇编代码,或者是否有任何技巧可以找到 return 地址?
我是汇编新手。
R14
或其他名称 LR
持有 return 地址。在左边你可以在图片中看到它。是0x08000287
.
您可以在 Cortex-M 文档中找到所有答案
http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.ddi0337h/Chdedegj.html
调用函数时,R14会被调用("BL"或"BLX")指令后的地址覆盖。如果该函数不调用任何其他函数,R14 将 经常 在其持续时间内保留 return 地址。此外,如果函数尾调用另一个函数,则尾调用可以替换为分支("B" 或 "BX"),R14 保存原始调用者的 return 地址。如果一个函数对另一个函数进行非尾调用,则有必要在此之前的某个时间保存 R14 "somewhere"(通常是堆栈,但可能保存到另一个先前使用的调用者保存的寄存器),并检索稍后从堆栈中获取该值,但如果启用优化,R14 的保存位置通常是不可预测的。
一些编译器可能有一种模式可以一致地堆叠事物以使其足够可用,但代码将非常依赖于编译器。最有可能成功的技术可能是做类似的事情:
extern int getStackAddress(uint8_t **addr); // Always returns zero
void myFunction(...whavever...)
{
uint8_t *returnAddress;
if (getStackAddress(&returnAddress)) return; // Put this first.
}
其中 getStackAddress
将是一个机器代码函数,它将 R14 存储到 R0 中的地址,将 R0 加载为零,然后分支到 R14。可能跟随它的代码序列相对较少,如果代码检查存储在 returnAddress
中的地址处的指令并识别这些代码序列之一,它就会知道 return 地址用于myFunction
存储在适合所讨论序列的位置。例如,如果它看到:
test r0,r0
be ...
pop {r0,pc}
它会知道调用者的地址在堆栈中是第二个。同样,如果它看到:
cmp r0,#0
bne somewhere:
somewhere: ; Compute address based on lower byte of bne
pop {r0,r1,r2,r4,r5,pc}
那么它就知道来电者的地址是第六个。
编译器可以使用一些指令来测试寄存器是否为零,一些编译器可能使用 be
,而其他编译器可能使用 bne
,但对于上面的代码,编译器可能会使用上述模式,因此计算 pop
指令中设置了多少位将揭示堆栈上 return 地址的位置。直到运行时才知道这个测试是否真的有效,但在它声称识别 return 地址的情况下,它实际上应该是正确的。