我必须更改function_b
的指定部分,以便它以程序打印的方式更改堆栈:
Executing function_a
Executing function_b
Finished!
此时它还会在Executed function_b
和Executing function_b
之间打印Finished!
。
我有以下代码,我必须在其中所说的部分填写内容// ...在此处插入代码
#include <stdio.h>
void function_b(void){
char buffer[4];
// ... insert code here
fprintf(stdout, "Executing function_b\n");
}
void function_a(void) {
int beacon = 0x0b1c2d3;
fprintf(stdout, "Executing function_a\n");
function_b();
fprintf(stdout, "Executed function_b\n");
}
int main(void) {
function_a();
fprintf(stdout, "Finished!\n");
return 0;
}
我正在使用带有gcc编译器的Ubuntu Linux。我使用以下选项编译程序:-g -fno-stack-protector -fno-omit-frame-pointer
。我正在使用英特尔处理器。
答案 0 :(得分:0)
这是一个解决方案,在各种环境下并不完全稳定,但适用于Windows / MinGW64上的x86_64处理器。 它可能不适合你开箱即用,但你仍然可能想要使用类似的方法。
void function_b(void) {
char buffer[4];
buffer[0] = 0xa1; // part 1
buffer[1] = 0xb2;
buffer[2] = 0xc3;
buffer[3] = 0x04;
register int * rsp asm ("rsp"); // part 2
register size_t r10 asm ("r10");
r10 = 0;
while (*rsp != 0x04c3b2a1) {rsp++; r10++;} // part 3
while (*rsp != 0x00b1c2d3) rsp++; // part 4
rsp -= r10; // part 5
rsp = (int *) ((size_t) rsp & ~0xF); // part 6
fprintf(stdout, "Executing function_b\n");
}
诀窍是function_a
和function_b
中的每一个只有一个局部变量,我们只需在内存中搜索即可找到该变量的地址。
首先,我们在缓冲区中放置一个签名,让它成为4字节整数0x04c3b2a1(记住x86_64是little-endian)。
之后,我们declare两个变量来表示寄存器:rsp
是堆栈指针,而r10
只是一些未使用的寄存器。
这允许以后不在代码中使用asm
语句,同时仍然可以直接使用寄存器。
重要的是变量实际上并不占用堆栈内存,它们是对处理器寄存器本身的引用。
之后,我们以4字节为增量移动堆栈指针(因为int
的大小为4字节),直到我们到达buffer
。我们必须记住堆栈指针到第一个变量的偏移量,我们使用r10
来存储它。
接下来,我们想知道堆栈中function_b
和function_a
的实例有多远。一个很好的近似值是buffer
和beacon
的距离,因此我们现在搜索beacon
。
之后,我们必须从beacon
的第一个变量function_a
推回到堆栈上整个function_a
的实例的开头。
我们通过减去r10
。
最后,这里有一个werider位。
至少在我的配置中,堆栈恰好是16字节对齐,而buffer
数组对齐到16字节块的左边,beacon
变量对齐到右边的这样的块。
或者是具有类似效果和不同解释的东西?
无论如何,我们只需清除堆栈指针的最后四位,使其再次对齐16字节。
32位GCC没有为我调整任何内容,因此您可能希望跳过或更改此行。
在处理解决方案时,我发现以下宏非常有用:
#ifdef DEBUG
#define show_sp() \
do { \
register void * rsp asm ("rsp"); \
fprintf(stdout, "stack pointer is %016X\n", rsp); \
} while (0);
#else
#define show_sp() do{}while(0);
#endif
在此之后,当您在代码中插入show_sp();
并使用-DDEBUG
进行编译时,它会在相应时刻打印堆栈指针的值。
在没有-DDEBUG
的情况下进行编译时,宏只会编译为空语句。
当然,其他变量和寄存器也可以用类似的方式打印。
答案 1 :(得分:0)
好吧,假设}
和function_a
的结尾(即function_b
行的代码)相同
尽管函数A
和B
不对称,我们可以假设这是因为它具有相同的签名(没有参数,没有返回值),相同的调用约定和相同的局部变量大小(4字节) - int beacon = 0x0b1c2d3
vs char buffer[4];
)并且通过优化 - 两者都必须删除,因为未使用。但是我们不能在function_b
中使用额外的局部变量来打破这个假设。这里最有问题的一点 - function_A
或function_B
将使用非易失性寄存器(结果将其保存在 prologue 中并在结尾中恢复) - 但是看起来这里没有这个地方。
所以我的下一个代码基于这个假设 - epilogueA == epilogueB
(@Gassa的真正解决方案也基于它。
还需要非常明确地指出function_a
和function_b
不能内联。这非常重要 - 没有这个解决方案是不可能的。所以我让你自己为function_a
和function_b
添加noinline属性。注意 - 不是代码更改而是属性添加,此任务的作者隐含地暗示但未明确说明。不知道 GCC 标记的功能如何作为noinline,但在 CL __declspec(noinline)
中用于此。
我为 CL 编译器编写的下一个代码,其中存在下一个内部函数
void * _AddressOfReturnAddress();
但我认为GCC
也必须拥有此功能的analog。我也用
void * _ReturnAddress();
但是真的_ReturnAddress() == *(void**)_AddressOfReturnAddress()
,我们只能使用_AddressOfReturnAddress()
。只需使用_ReturnAddress()
make source(但不是二进制 - 它相等)代码更小,更易读。
下一个代码适用于x86和x64。并且此代码使用任何优化工作(测试)。
尽管我使用了2个全局变量 - 代码是线程安全的 - 我们真的可以从并发多个线程中调用main
,多次调用它 - 但是所有这些都是正确的(当然我只能在开始时说)如果epilogueA == epilogueB
)
希望代码中的评论足够自我解释
__declspec(noinline) void function_b(void){
char buffer[4];
buffer[0] = 0;
static void *IPa, *IPb;
// save the IPa address
_InterlockedCompareExchangePointer(&IPa, _ReturnAddress(), 0);
if (_ReturnAddress() == IPa)
{
// we called from function_a
function_b();
// <-- IPb
if (_ReturnAddress() == IPa)
{
// we called from function_a, change return address for return to IPb instead IPa
*(void**)_AddressOfReturnAddress() = IPb;
return;
}
// we at stack of function_a here.
// we must be really at point IPa
// and execute fprintf(stdout, "Executed function_b\n"); + '}' (epilogueA)
// but we will execute fprintf(stdout, "Executing function_b\n"); + '}' (epilogueB)
// assume that epilogueA == epilogueB
}
else
{
// we called from function_b
IPb = _ReturnAddress();
return;
}
fprintf(stdout, "Executing function_b\n");
// epilogueB
}
__declspec(noinline) void function_a(void) {
int beacon = 0x0b1c2d3;
fprintf(stdout, "Executing function_a\n");
function_b();
// <-- IPa
fprintf(stdout, "Executed function_b\n");
// epilogueA
}
int main(void) {
function_a();
fprintf(stdout, "Finished!\n");
return 0;
}