【问题标题】:forcing stack w/i 32bit when -m64 -mcmodel=small当 -m64 -mcmodel=small 时强制使用 32 位堆栈
【发布时间】:2010-05-11 01:55:53
【问题描述】:

具有必须为多个平台编译为 32 位和 64 位的 C 源代码。 采用缓冲区地址的结构 - 需要将地址放入 32 位值中。

显然,在可能的情况下,这些结构将使用自然大小的 void * 或 char * 指针。 但是对于某些部分,api 将这些指针的大小指定为 32 位。

在带有 -m64 -mcmodel=small 的 x86_64 linux 上,静态数据和 malloc() 的数据都在 2Gb 范围内。然而,堆栈上的数据仍然在高内存中开始。

所以给定一个小工具 _to_32() 例如:

int _to_32( long l ) {
  int i = l & 0xffffffff;
  assert( i == l );
  return i;
}

然后:

char *cp = malloc( 100 );
int a = _to_32( cp );

将可靠地工作,就像这样:

static char buff[ 100 ];
int a = _to_32( buff );

但是:

char buff[ 100 ];
int a = _to_32( buff );

assert() 将失败。

任何人都可以在不编写自定义链接器脚本的情况下解决这个问题?

或任何关于如何为堆栈数据安排链接器部分的想法,都会出现在链接器脚本的此部分中:

.lbss   :
{
  *(.dynlbss)
  *(.lbss .lbss.* .gnu.linkonce.lb.*)
  *(LARGE_COMMON)
}

谢谢!

【问题讨论】:

    标签: 32bit-64bit


    【解决方案1】:

    堆栈位置很可能由操作系统指定,与链接器无关。

    我无法想象您为什么试图将 64 位机器上的指针强制转换为 32 位。当您与可能在另一个架构上运行的东西共享数据并保存到文件或通过网络发送时,结构的内存布局非常重要,但是几乎没有正当理由可以将指针从一台计算机发送到其他。调试是我想到的唯一正当理由。

    即使存储一个指针以供稍后在同一台机器上运行程序使用,也几乎肯定是错误的,因为加载程序的位置可能不同。对这样一个指针的任何使用都是未定义的,也是不可预测的。

    【讨论】:

    • 是的,堆栈是由加载器布局的——基于链接器创建的 elf 头文件中的指令。上面已经解释了为什么需要在 64 位和 32 位指针之间移动,以及为确保指针值保持在 32 位范围内并在此条件失败时检测(通过 assert())所采取的步骤。和内存布局由调用例程定义——它可能使用不同的语言——并且完全依赖于 api 规范。不是一台不同的机器,从来没有说过这样的话。也不存储指针。
    • @choasless:你没有解释为什么需要在不同的指针大小之间移动,只是你需要。重新阅读您的问题后,我相信您可能将指针的大小与其指向的数据类型的大小混淆了。大多数常见架构的指针大小相同,无论它们指向什么。例外情况是少数处理器具有用于字节大小的事物和 16 位大小的事物的特殊内存。
    • 是必要的,因为 api 提供了一个 32 位区域用于将指针传递给数据。 20 多年后,我确信我知道数据和数据指针之间的区别。这适用于必须遵守该 api 的代码,其中包括允许指针使用 32 位的结构。如上所述,如果数据是 malloc()d 或静态数据,则它属于 -mcmodel=small 控件。这就是为什么在这里询问是否有人有任何实际有用的 cmets 关于加载程序如何确定放置堆栈地址的位置以及如何在链接器脚本中影响它。
    • 如果 API 确实指定指针必须是 32 位,那么它就是一个损坏的 API。虽然我仍然不确定您正在使用什么,但我想知道查找表是否足够。创建一个 void * map_32_to_64[MAX_THINGS];作为 64 位的条件代码,只需在通常在 API 中使用指针的地方使用 uint32_t。这并不好,因为在其中找到一个指针并找到一个可以使用的空插槽更加困难。或者你可以创建一个数组,你通常会传递指针。
    • 你真的看过我发布的任何内容吗? api就是这样-是否认为它“损坏”是无关紧要的。如果我要更改支持此 api 的无数代码,那么我可以将其更改为 malloc() 空间因此落在 2Gb 边界内。感谢您的评论。
    【解决方案2】:

    简短的答案似乎是没有简单的答案。至少没有简单的方法来重新分配堆栈指针的范围/位置。

    加载器“ld-linux.so”在进程激活的早期阶段获取 hurd 加载器中的地址 - 在 glibc 源代码 elf/ 和 sysdeps/x86_64/ 中搜索 elf_machine_load_address() 和 elf_machine_runtime_setup()。

    这发生在调用您的 _start() 条目和相关设置以调用您的 main() 的序言​​中,不适合胆小的人,即使我无法说服自己这是一条安全的路线。

    当它发生时 - 解决方案以其他一些老派的技巧出现......指针通货紧缩/通货膨胀......

    如果使用 -mcmodel=small,那么自动变量、alloca() 地址以及 argv[] 和 envp 之类的东西都是从高内存分配的,堆栈将从高内存向下增长。这些地址在此示例代码中得到验证:

    #include <stdlib.h>
    #include <stdio.h>
    #include <alloca.h>
    
    extern char etext, edata, end;
    char global_buffer[128];
    
    int main( int argc, const char *argv[], const char *envp )
    {
      char stack_buffer[128];
      static char static_buffer[128];
      char *cp = malloc( 128 );
      char *ap = alloca( 128 );
      char *xp = "STRING CONSTANT";
    
      printf("argv[0] %p\n",argv[0]);
      printf("envp    %p\n",envp);
      printf("stack   %p\n",stack_buffer);
      printf("global  %p\n",global_buffer);
      printf("static  %p\n",static_buffer);
      printf("malloc  %p\n",cp);
      printf("alloca  %p\n",ap);
      printf("const   %p\n",xp);
      printf("printf  %p\n",printf);
    
      printf("First address past:\n");
      printf("    program text (etext)      %p\n", &etext);
      printf("    initialized data (edata)  %p\n", &edata);
      printf("    uninitialized data (end)  %p\n", &end);
    }
    

    产生这个输出:

    argv[0] 0x7fff1e5e7d99 环境 0x7fff1e5e6c18 堆栈 0x7fff1e5e6a80 全局 0x6010e0 静态0x601060 malloc 0x602010 分配 0x7fff1e5e69d0 常量 0x400850 printf 0x4004b0 过去的第一个地址: 程序文本 (etext) 0x400846 初始化数据(eddata)0x601030 未初始化数据(结束)0x601160

    对结构的 32 位部分的所有访问都必须使用 inflate() 和 deflate() 例程进行包装,例如:

    void *inflate( unsigned long );
    unsigned int deflate( void *);
    

    deflate() 测试设置在 0x7fff00000000 范围内的位并标记指针,以便 inflate() 识别如何重构实际指针。

    如果有人同样必须支持 64 位指针的 32 位存储结构,希望对您有所帮助。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2011-11-17
      • 2015-10-13
      • 2016-03-07
      • 2019-07-29
      • 2021-12-25
      • 2021-08-04
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多