遗留的gcc编译器问题

7
我们正在使用一款基于gcc 2.6.0的遗留编译器,用于交叉编译我们仍在使用的旧嵌入式处理器(是的,自1994年以来它仍在使用!)。负责为该芯片进行gcc移植的工程师已经离开很久了。虽然我们可能能够从网络上某个地方恢复gcc 2.6.0的源代码,但是针对这个芯片的变更集却已经消失在公司历史的大厅中。我们一直勉强应对,因为编译器仍然可以运行并生成可用的可执行文件,但是在linux内核2.6.25(以及2.6.26)之后,即使不带任何参数或只带-v,它也会失败并显示错误信息gcc: virtual memory exhausted。我已经使用2.6.24内核重新启动我的开发系统,并且编译器可以正常工作(使用2.6.25重新启动则失败)。
我们有一个系统保持在2.6.24版本,只是为了构建该芯片的程序,但是如果Linux世界继续向前发展,我们将无法再重建一个能够运行编译器的系统(例如,我们的2.6.24系统停止使用,而我们无法在新系统上安装和运行2.6.24,因为某些软件部分不再可用),这让我们感到有些不安。
有没有人有什么想法可以让我们在更现代的安装中运行这个遗留编译器?

好的,更多信息:

看起来brk随机化是罪魁祸首,旧版本的gcc调用brk()来改变数据段的结尾,而这现在失败了,导致旧版的gcc报告“虚拟内存耗尽”。有几种记录的方法可以禁用brk随机化:

  • sudo echo 0 > /proc/sys/kernel/randomize_va_space

  • sudo sysctl -w kernel.randomize_va_space=0

  • 使用setarch i386 -R tcsh(或“-R-L”)启动新的shell

我已经尝试过它们,它们似乎确实有影响,因为brk()返回值与没有它们时不同(在内核2.6.25和2.6.26上都试过),但是brk()仍然失败,所以旧版gcc仍然失败:-(。

此外,我设置了vm.legacy_va_layout=1vm.overcommit_memory=2,但没有改变,并且我重新启动了保存在/ etc / sysctl.conf中的vm.legacy_va_layout=1kernel.randomize_va_space=0。仍然没有变化。

编辑:

在内核2.6.26(和2.6.25)上使用kernel.randomize_va_space=0会导致strace legacy-gcc报告以下brk()调用:

brk(0x80556d4) = 0x8056000

这表明brk()失败了,但看起来它失败是因为数据段已经超出了所请求的范围。使用objdump,我可以看到数据段应该以0x805518c结束,而失败的brk()指示数据段当前以0x8056000结束:

章节: Idx 名称 大小 VMA LMA 文件偏移 对齐 0 .interp 00000013 080480d4 080480d4 000000d4 2**0 内容,分配,加载,只读,数据 1 .hash 000001a0 080480e8 080480e8 000000e8 2**2 内容,分配,加载,只读,数据 2 .dynsym 00000410 08048288 08048288 00000288 2**2 内容,分配,加载,只读,数据 3 .dynstr 0000020e 08048698 08048698 00000698 2**0 内容,分配,加载,只读,数据 4 .rel.bss 00000038 080488a8 080488a8 000008a8 2**2 内容,分配,加载,只读,数据 5 .rel.plt 00000158 080488e0 080488e0 000008e0 2**2 内容,分配,加载,只读,数据 6 .init 00000008 08048a40 08048a40 00000a40 2**4 内容,分配,加载,只读,代码 7 .plt 000002c0 08048a48 08048a48 00000a48 2**2 内容,分配,加载,只读,代码 8 .text 000086cc 08048d10 08048d10 00000d10 2**4 内容,分配,加载,只读,代码 9 .fini 00000008 080513e0 080513e0 000093e0 2**4 内容,分配,加载,只读,代码 10 .rodata 000027d0 080513e8 080513e8 000093e8 2**0 内容,分配,加载,只读,数据 11 .data 000005d4 08054bb8 08054bb8 0000bbb8 2**2 内容,分配,加载,数据 12 .ctors 00000008 0805518c 0805518c 0000c18c 2**2 内容,分配,加载,数据 13 .dtors 00000008 08055194 08055194 0000c194 2**2 内容,分配,加载,数据 14 .got 000000b8 0805519c 0805519c 0000c19c 2**2 内容,分配,加载,数据 15 .dynamic 00000088 08055254 08055254 0000c254 2**2 内容,分配,加载,数据 16 .bss 000003b8 080552dc 080552dc 0000c2dc 2**3 分配 17 .note 00000064 00000000 00000000 0000c2dc 2**0 内容,只读 18 .comment 00000062 00000000 00000000 0000c340 2**0 内容,只读 符号表: 没有符号

编辑:

回应下面ephemient的评论:“如此奇怪地将GCC视为没有源代码的二进制文件”!

因此,我使用strace、objdump、gdb和有限的386汇编语言和体系结构理解,已经追踪到遗留代码中第一个malloc调用的问题。遗留gcc调用malloc,它返回NULL,导致stderr上出现“虚拟内存耗尽”的消息。这个malloc在libc.so.5中,它调用getenv多次,并最终调用brk()...我猜是为了增加堆...但失败了。

由此我只能推测问题不仅仅是brk随机化,或者我尽管设置了randomize_va_space=0和legacy_va_layout=1 sysctl设置,但并没有完全禁用brk随机化。


澄清关于变更集消失的情况---你是否丢失了(编译器的部分)源代码? - Norman Ramsey
我理解你的痛苦,但我认为我们应该更加担心其他风险,而不是未来无法访问特定2.6内核版本的Linux源代码的风险。 - unwind
嗯,所以它是动态链接到 libc.so.5,问题出在这里?这是一个可怕的hack,但是glibc应该足够兼容,如果你做一个 ln -s libc.so.6 libc.so.5,你的代码可能仍然可以运行。 - ephemient
那确实是个丑陋的Hack,但就像我的一个朋友说的:“只要能用,它就很美。”不幸的是,它不起作用(旧版gcc报告“无法处理重定位类型<NULL>”)...所以它仍然丑陋 :-) 这并不奇怪,因为libc5->libc6是一件大事。 - JimKleck
我看到了最近提交的Linux内核,似乎与这个问题有关:http://git.kernel.org/linus/4471a675dfc7ca676c165079e91c712b09dc9ce4 - CesarB
6个回答

12

在虚拟机上安装Linux和旧版本的GCC。


3
将其放入VMWare Player(或其他选择,但我会选择VMWare),然后您可以随意从机器上取下并移动它。备份也很简单,只需保存整个VM目录即可。 - paxdiablo

5

您有这个自定义编译器的源代码吗?如果您能够恢复2.6.0基线(这应该相对容易),那么diff和patch应该可以恢复您的更改集。

然后,我建议使用该更改集针对最新的gcc构建新版本。然后将其放置在配置控制下。

抱歉,不是要大声喊叫。只是我已经说了同样的话30年了。


3
别克,不要犹豫,告诉我们你真实的感受。 - dmckee --- ex-moderator kitten
如果他们在30年后仍然不听你的话,也许是时候考虑放弃了 :-) - paxdiablo
它的阴险之处在于:偶尔会有某个人得到它。这足以让你上瘾,但又不足以让你放松下来。 - dmckee --- ex-moderator kitten
3
丢失代码的情况比你想象的要普遍得多。亚特兰大有一家公司专门为那些丢失源代码的人反编译二进制文件。你可不想让这些好心的人失业吧? - Norman Ramsey
@Pax,如果我不是真的很固执,我会选择40年来从事编程而不是找一份真正的工作吗? - Charlie Martin
显示剩余5条评论

2
能否对 gcc-2.6.0 可执行文件进行 strace?它可能正在读取 /proc/$$/maps,并且在输出以微不足道的方式更改时感到困惑。类似的问题在2.6.28和2.6.29之间最近被注意到
如果是这样,您可以修改 /usr/src/linux/fs/proc/task_mmu.c 或其周边代码来恢复旧的输出,或者设置一些 $LD_PRELOAD 来欺骗 gcc 读取另一个文件。

编辑

由于您提到了 brk... CONFIG_COMPAT_BRK 使默认值为 kernel.randomize_va_space=1 而不是 2,但仍会随机化除堆(brk)之外的所有内容。
看一下是否可以通过执行 echo 0 > /proc/sys/kernel/randomize_va_space 或者 sysctl kernel.randomize_va_space=0(等效)解决问题。
如果可以,将 kernel.randomize_va_space = 0 添加到 /etc/sysctl.conf 或者在内核命令行中添加 norandmaps(等效),然后再次享受愉快的使用体验。

很奇怪把GCC当作没有源代码的二进制文件来处理...如果可能的话,我肯定会选择Charlie的路线。 - ephemient
针对您上面的编辑,我尝试了一下...但是没有成功。我在问题中添加了更多的注释。实际上看起来是遗留的gcc正在尝试更改数据段的末尾,但系统已经认为末尾在遗留的gcc尝试扩展到的位置之外。 - JimKleck

1

我发现了this,想到了你的问题。也许你可以找到一种方法来处理二进制文件,将其转换为ELF格式?或者这个方法不相关,但是使用objdump工具可能会提供更多信息。

你能看一下进程内存映射吗?


你提供的链接似乎更适用于在嵌入式Linux上运行程序,而我正在PC上运行旧版gcc。但是我确实尝试了objdump,旧版gcc是elf32-i386。 - JimKleck

1

我已经解决了一个问题...虽然不是完整的解决方案,但它确实解决了我在旧版gcc中遇到的原始问题。

在.plt(过程链接表)中的每个libc调用上设置断点后,我发现malloc(在libc.so.5中)调用getenv()来获取:

    MALLOC_TRIM_THRESHOLD_
    MALLOC_TOP_PAD_
    MALLOC_MMAP_THRESHOLD_
    MALLOC_MMAP_MAX_
    MALLOC_CHECK_

所以我在网上搜索了这些内容,并找到了this,建议如下:

    setenv MALLOC_TOP_PAD_ 536870912

然后旧版gcc就可以工作了!!!

但还没有成功,它在构建链接之前失败了,所以我们的旧版nld还有其他问题 :-( 它报告如下:

    Virtual memory exceeded in `new'

在/etc/sysctl.conf中,我有:

    kernel.randomize_va_space=0
    vm.legacy_va_layout=1

如果更改为:

kernel.randomize_va_space=1
vm.legacy_va_layout=0

但是如果条件不符合,则不执行。

kernel.randomize_va_space=2

有人建议使用“ldd”查看共享库依赖项:传统的gcc只需要libc5,但传统的nld还需要libg++.so.27、libstdc++.so.27、libm.so.5,显然还有一个libc5版本的libg++.so.27(libg++27-altdev??),那libc5-compat呢?

所以,正如我所说,还没有完全解决问题……但已经接近了。我可能会发布一个关于nld问题的新问题。

编辑:

我原本打算不“接受”这个答案,因为我仍然有一个相应的传统链接器问题,但为了至少在这个问题上得到一些确定性,我正在重新考虑这个立场。

感谢以下人员:

  • an0nym0usc0ward建议使用vm(最终可能成为被接受的答案)
  • ephemient建议使用strace,并帮助使用stackoverflow
  • shodanex建议使用objdump

编辑

以下是我最近学到的内容,现在我将接受虚拟机解决方案,因为我无法以其他方式完全解决它(至少在分配给这个问题的时间内无法解决)。

新的内核有一个CONFIG_COMPAT_BRK构建标志,允许使用libc5,因此假定使用此标志构建新的内核将解决问题(通过浏览内核源代码,看起来会这样,但是我不确定,因为我没有跟随所有路径)。还有另一种记录的方法可以允许在运行时使用libc5(而不是在内核构建时):sudo sysctl -w kernel.randomize_va_space=0。然而,这并不能完全解决问题,并且一些(大多数?)libc5应用程序仍然会崩溃,例如我们的遗留编译器和链接器。这似乎是由于新旧内核之间对齐假设的差异造成的。我已经对链接器二进制文件进行了补丁,在这个基础上让它认为它有一个更大的bss部分,以便将bss的末尾提高到页面边界,并且当sysctl变量kernel.randomize_va_space=0时,在新的内核上运行正常。这对我来说不是一个令人满意的解决方案,因为我正在盲目地修补一个关键的二进制可执行文件,即使在新内核上运行补丁链接器产生与在旧内核上运行的原始链接器相同的比特完全相同的输出,这也不能证明其他链接器输入(即我们更改正在链接的程序)也会产生相同的结果。

0

你能不能简单地制作一个可以重新安装系统的光盘镜像或虚拟机呢?


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接