gpt4 book ai didi

c - 为什么在ubuntu64中反转堆栈中元素的地址?

转载 作者:太空狗 更新时间:2023-10-29 16:07:55 25 4
gpt4 key购买 nike

我编写了一个简单的程序来打印出堆栈中元素的地址

#include <stdio.h>
#include <memory.h>
void f(int i,int j,int k)
{
int *pi = (int*)malloc(sizeof(int));
int a =20;
printf("%p,%p,%p,%p,%p\n",&i,&j,&k,&a,pi);
}

int main()
{
f(1,2,3);
return 0;
}

输出:(在ubuntu64中,意外)
0x7fff4e3ca5dc,0x7fff4e3ca5d8,0x7fff4e3ca5d4,0x7fff4e3ca5e4,0x2052010

输出:(在ubuntu32中,如预期)
0xbf9525f0,0xbf9525f4,0xbf9525f8,0xbf9525d8,0x931f008

ubuntu64的环境:
$uname -a
Linux 3.8.0-26-generic #38-Ubuntu SMP Mon Jun 17 21:43:33 UTC 2013 x86_64 x86_64 x86_64 GNU/Linux
$gcc -v
Target: x86_64-linux-gnu
gcc version 4.8.1 (Ubuntu 4.8.1-2ubuntu1~13.04)

根据上面的图表,元素被推送到堆栈越早,它将找到的地址就越高,
如果使用调用约定cdecl,最右边的参数将首先推送到堆栈。
本地变量应该在参数被推后被推送到堆栈中
但在ubuntu64中输出与预期相反:
the address of k is :0x7fff4e3ca5d4   //<---should have been pushed to the stack first
the address of j is :0x7fff4e3ca5d8
the address of i is :0x7fff4e3ca5dc
the address of a is :0x7fff4e3ca5e4 //<---should have been pushed to the stack after i,j,k

有什么想法吗?

最佳答案

尽管已经为这两种体系结构定义了一个清晰的abi,但编译器并不保证这一点得到尊重。你可能想知道为什么,原因通常是表现。就速度而言,将变量传递到堆栈中比使用寄存器要昂贵,因为应用程序需要访问内存来检索它们。这种习惯的另一个例子是编译器如何使用EBP/RBP寄存器。EBP/RBP应该是包含帧指针的寄存器,即堆栈基址。堆栈基址寄存器允许容易访问局部变量。然而,帧指针寄存器通常用作提高性能的通用寄存器。这样就避免了保存、设置和恢复帧指针的指令;它还使一个额外的寄存器在许多函数中可用,在x86_32体系结构中尤其重要,因为通常程序都渴望寄存器。主要的缺点是在某些机器上无法进行调试。有关更多信息,请检查gcc的-fomit-frame-pointer选项。
x86_32和x86_64之间的调用函数非常不同。最相关的区别是x86_64尝试使用通用寄存器来传递函数参数,只有在没有可用的寄存器或参数大于80字节时,它才会使用堆栈。
我们从x86_32 ABI开始,我稍微更改了您的示例:

#include <stdio.h>
#include <stddef.h>
#include <stdint.h>

#if defined(__i386__)
#define STACK_POINTER "ESP"
#define FRAME_POINTER "EBP"
#elif defined(__x86_64__)
#define STACK_POINTER "RSP"
#define FRAME_POINTER "RBP"
#else
#error Architecture not supported yet!!
#endif

void foo(int i,int j,int k)
{
int a =20;
uint64_t stack=0, frame_pointer=0;

// Retrieve stack
asm volatile(
#if defined (__i386__)
"mov %%esp, %0\n"
"mov %%ebp, %1\n"
#else
"mov %%rsp, %0\n"
"mov %%rbp, %1\n"
#endif
: "=m"(stack), "=m"(frame_pointer)
:
: "memory");
// retrieve paramters x86_64
#if defined (__x86_64__)

int i_reg=-1, j_reg=-1, k_reg=-1;

asm volatile ( "mov %%rdi, %0\n"
"mov %%rsi, %1\n"
"mov %%rdx, %2\n"
: "=m"(i_reg), "=m"(j_reg), "=m"(k_reg)
:
: "memory");
#endif

printf("%s=%p %s=%p\n", STACK_POINTER, (void*)stack, FRAME_POINTER, (void*)frame_pointer);
printf("%d, %d, %d\n", i, j, k);
printf("%p\n%p\n%p\n%p\n",&i,&j,&k,&a);


#if defined (__i386__)
// Calling convention c
// EBP --> Saved EBP
char * EBP=(char*)frame_pointer;
printf("Function return address : 0x%x \n", *(unsigned int*)(EBP +4));
printf("- i=%d &i=%p \n",*(int*)(EBP+8) , EBP+8 );
printf("- j=%d &j=%p \n",*(int*)(EBP+ 12), EBP+12);
printf("- k=%d &k=%p \n",*(int*)(EBP+ 16), EBP+16);
#else
printf("- i=%d &i=%p \n",i_reg, &i );
printf("- j=%d &j=%p \n",j_reg, &j );
printf("- k=%d &k=%p \n",k_reg ,&k );
#endif
}

int main()
{
foo(1,2,3);
return 0;
}

foo正在使用esp寄存器指向堆栈的顶部。ebp寄存器充当“基指针”。所有参数已按相反顺序推入堆栈。main传递给foo的参数和foo中的局部变量都可以作为基指针的偏移引用。调用foo之后,堆栈应该如下所示:。
假设编译器正在使用堆栈指针,我们可以通过将偏移量4字节相加到 EBP寄存器来访问函数参数。注意,第一个参数位于偏移量8,因为调用指令在堆栈中推送调用函数的返回地址。
  printf("Function return address : 0x%x  \n",      *(unsigned int*)(EBP +4)); 
printf("- i=%d &i=%p \n",*(int*)(EBP+8) , EBP+8 );
printf("- j=%d &j=%p \n",*(int*)(EBP+ 12), EBP+12);
printf("- k=%d &k=%p \n",*(int*)(EBP+ 16), EBP+16);

这或多或少是如何将参数传递给x86_32中的函数的。
在x86_64中有更多可用的寄存器,使用它们传递函数的参数是有意义的。x86_64 ABI可以在这里找到: http://www.uclibc.org/docs/psABI-x86_64.pdf。电话会议从第14页开始。
首先将参数划分为类。每个参数的类决定了传递给被调用函数的方式。一些最相关的是:
整型这个类由整型组成,整型定义为
通用寄存器。例如(int、long、bool)
SSE类由进入SSE寄存器的类型组成。(浮点数,双精度)
该类由进入SSE寄存器的类型组成,并且可以
在其中最重要的一半被传递和返回。(浮点数:128,m128,m256)
没有_类在
算法。它将用于填充、空结构和联合。
内存这个类包含将在内存中传递和返回的类型
通过堆栈(结构类型)
一旦a参数被分配给一个类,它就会根据
这些规则:
内存,在堆栈上传递参数。
整数,使用序列%rdi、%rsi、%rdx、%rcx、%r8和%r9的下一个可用寄存器。
SSE,使用下一个可用的SSE寄存器,寄存器的顺序是从%xmm0到%xmm7。
sseup,八个字节被传递到最后使用的sse寄存器的上半部分。
如果一个八字节的参数没有可用的寄存器,那么
参数在堆栈上传递。如果已经为某些
八个字节这样的参数,赋值被还原。一旦分配了寄存器,内存中传递的参数就会以相反的顺序推送到堆栈上。
因为您传递的是int变量,所以参数将被插入到通用寄存器中。
%rdi --> i 
%rsi --> j
%rdx --> k

所以你可以用下面的代码来检索它们:
#if defined (__x86_64__)

int i_reg=-1, j_reg=-1, k_reg=-1;

asm volatile ( "mov %%rdi, %0\n"
"mov %%rsi, %1\n"
"mov %%rdx, %2\n"
: "=m"(i_reg), "=m"(j_reg), "=m"(k_reg)
:
: "memory");
#endif

我希望我已经说清楚了。
总之,
为什么栈中元素的地址在ubuntu64中是反向的?
因为它们没有存储在堆栈中。以这种方式检索的地址是调用函数的本地变量的地址。

关于c - 为什么在ubuntu64中反转堆栈中元素的地址?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/17900910/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com