前言

首先不知道什么是glibc的可以先看看这里,或这里。如果看了还是不知道的话可能下面就不适合你看了 - -

然后,这篇文章会大概讲一下怎么编译一个带debug_info的glibc以及编了以后要怎么用。

以上。

正文

首先,第一步是要得到glibc的源码,可以在这里下载,建议先下载和自己系统相同版本的(这里假设你的系统是Linux,不过如果是Win**ws的话编来有个**用啊 - -)

如果要看自己系统的libc版本的话可以用命令(我这里的是ubuntu,其他的应该也一样吧,方法不唯一)

    ls -l /lib/ld-linux.so.2


像我是Ubuntu18.04的话版本是2.27。

下载好以后解压然后cd进去(以下操作都是在这个目录或里面的子目录里面进行),然后先创建几个文件夹

    mkdir build build32 x64 x86

其中

  • build目录是用来放编译64位版本的中间文件的
  • build32目录是用来放编译32位版本的中间文件的
  • x64目录是编译好64位的版本后install的位置
  • x32目录是编译好32位的版本后install的位置
    (名字和位置可以改,知道哪个是哪个就好了)

编译64位的glibc

(用过make的话下面都会看得懂的 - -)
首先进入build目录,就是编译64位的目录,然后configure一下,命令是:

# in glibc-x.xx/build/
CC="gcc" CXX="g++" \             
    CFLAGS="-g -g3 -ggdb -gdwarf-4 -Og -Wno-error -fno-stack-protector" \
    CXXFLAGS="-g -g3 -ggdb -gdwarf-4 -Og -Wno-error -fno-stack-protector" \
    ../configure --prefix=/path/to/install/glibc/x64 --disable-werror

上面的"-g -g3 -ggdb -gdwarf-4"指的是加上各种debug_info;"-Og"指不进行优化,这样的话编译出来的东西人才能更好地看懂;"-Wno-error "和" --disable-werror"是防止一些奇奇怪怪但有不怎么重要的error的(其实好像没什么用- -);"-fno-stack-protector"是我边2.23版本时遇到某一个error的时候加上的,如果编系统一样版本的话应该用不上。
"--prefix=/path/to/install/glibc/x64"这里要改成安装的目录,上面按着我的来的话就是那个新建的x64文件夹,注意这个一定要写而且不能写成跟系统安装的一样(不能写到"/usr",这个非常重要😅,不然会发生什么我也不知道)

configure成功以后就是make(注意有Error的话不算成功)

# 可根据电脑配置开多线程 -> make -j n  (n是线程数,如果电脑很闲的话建议是核数的两倍)
    make 

make成功以后就是make install(注意有Error的话不算成功)

    make install

install完以后先看看安装的目录,如果有下面的东西的话说明应该是安装成功了。

然后build这个目录可以make clean删掉(如果你磁盘空间够大的话当我没说🤔)

编译32位的glibc

方法基本上跟64位的一样,只是configure要变一下,目录也要换成build32。命令:

    cd build32
# configure 换一下,具体说明下面说
    CC="gcc -m32" CXX="g++ -m32" \             
        CFLAGS="-g -g3 -ggdb -gdwarf-4 -Og -Wno-error -fno-stack-protector" \
        CXXFLAGS="-g -g3 -ggdb -gdwarf-4 -Og -Wno-error -fno-stack-protector" \
        ../configure --prefix=/path/to/glibc/result/glibc-x.xx/x86 --host=i686-linux-gnu --disable-werror
# configure成功后
    make
# make成功后
    make install
# install成功后,也可以直接删掉build32目录
    make clean

上面configure中加多了几个参数:"-m32"是指明gcc/g++编译32位;还有" --host=i686-linux-gnu"指明架构(应该是吧- -),这里不写i386是因为会报错(应该是太旧了)。然后为了i686这个东西还要多安装一些东西,不然会有一些奇奇怪怪的错(其实不知道是不是要全装的,反正嫌麻烦一次性全装了)

    sudo apt install binutils-i686-gnu gcc-i686-linux-gnu binutils-i686-gnu-dbg g++-i686-linux-gnu

如无以外安装完后x86目录跟上面的一样。然后build32这个目录可以删掉。

装好后注意源码不要删,不然千辛万苦弄出来的debug_info就没了。而且位置最好别改,不然。。。会比较麻烦(后面也会说一下)

测试装好了能不能用

先说明一下简单的原理。其实就是换库。

在上面安装好的文件中,有两个特别有用的文件,ld-linux.so.2(64位是ld-linux-x86-64.so.2) 和libc.so.6,(没有这两个的话可能就是没装成功了)

其中libc.so.6就是主要的库,我们平时用的一些库函数就是在这里面;ld-linux.so.2里面是放一些加载libc.so.6的函数(大概意思应该是这样吧)。实际上是两个库都要换的,但因为在编译时(注意是编译时)换了ld-linux.so.2的话,它会自动换libc.so.6,所以如果是编译过程中的换库的话只用换ld-linux.so.2就好。

下面是测试正文:首先随便写一个C的代码(hello world之类的,假设取名叫a.c好了),然后用下面命令编译("--dynamic-linker="后面的路径自己换成对应的):

# 32位
    gcc a.c -m32 -z lazy -g -o a \
		-Wl,--dynamic-linker=/path/to/ld-linux.so.2/installed
# 64位
    gcc a.c -z lazy -g -o a \
		-Wl,--dynamic-linker=/path/to/ld-linux-x86-64.so.2/installed

编出来的东西能运行的话说明可以了。

编译不同版本的glibc

因为太新太久等问题,编译不同于自己系统的glibc的话会有各种问题。通常来讲,编译比自己系统新的版本的话基本上是没什么问题的;而比自己系统就的版本的话,越就问题越多。

下面是在ubuntu18.04中编译时遇到的一些问题就解决方法:

。。。 。。。(算了太麻烦了有人看再写吧 - -)

编译出的带debug_info的glibc有什么用

首先一个是因为没事找事 (编一个标准库听起来很帅的感觉有没有,划掉)

其实是有些程序需要调试标准库里面的一些函数的时候有源码和一些结构信息的话会看着明显和好看一点,主要是用在调试上的,所以要借助调试的工具(我的是gdb)。下面放几个图就算了 - - (我的gdb因为装了插件所以跟原版的可能有些不一样,不过都是差不多的)

已编译好的程序的换库

这个就要一点技巧了🤔

首先要先创建一些符号链接:把安装好的32位的ld-linux.so.2链接到一个绝对路径有18个符号(ASCII)的地方,64位的ld-linux-x86-64.so.2链接到一个绝对路径有27个符号(ASCII)的地方,为什么有这个符号数量的限制?看下面。链接成什么名字自己取就好了,比如我的就:

搞好上面的之后就看那个编译好的程序,用vim打开(当然其他编辑器也可以)。32位程序的话搜索"/lib/ld-linux.so.2"(64位的话搜索"/lib/ld-linux-x86-64.so.2"),然后把这个东西改成上面连接号的文件的绝对路径,刚才为什么有字数限制就是因为这里改的字数要是一样的,不然会改乱了后面的程序就运行不起来。


改好后正常是运行不起来的(如果换的版本是跟系统一样的话可能可以运行,但其实是没有换到库的),原因是到这里只换了ld-linux.so.2这个链接用的库,没有换libc.so.6这个主体。所以要换这个库的话还要改以下环境变量(建议运行时加在前面,不要export,我也没试过,不知道会怎么样),加个"LD_LIBRARY_PATH=装好的glibc的lib目录"

在gdb中换库

首先在vim中按上面的方法改好ld这个库,然后gdb打开,同样改一下环境变量(注意是打开后在gdb里面改,不是打开gdb时改),命令:

# in gdb
(gdb)   set env LD_LIBRARY_PATH=装好的glibc的lib目录

然后库就换好了。

有一种情况是装好后源码移了位置(或者在别的机子上编好了移过来用)的,这样的话gdb会读不到debug_symbol的,这样的话就要重新设置一下源码的位置,用gdb的"directory"(可简写"dir")设置(怎么用的话google一下吧)。但是directory有个问题是它不会遍历子目录,所以如果要加整个glibc的话就要写脚本遍历(你很闲的话一个个加也是可以的🤔)。下面是我写的py脚本(可以参考一下或者改一下直接用也可以,下面会说怎么改)(而且要能用python的gdb才行,可以自己编):

#! -*- coding:utf8 -*-
import gdb
import os

def gdbDir_rec(f):
    fs = os.listdir(f)
    #print(f)
    result = []
    for f1 in fs:
        tmp_path = os.path.join(f,f1)
        if os.path.isdir(tmp_path):
            #gdb.execute('set dir ' + tmp_path,True,True)
            result.append(tmp_path)
            result += gdbDir_rec(tmp_path)
    return result

# arg in path of glibc installed(32/64), and path/.. is root of glibc source
class Gcdir(gdb.Command):
    def __init__(self):
        super(self.__class__, self).__init__("gcdir", gdb.COMMAND_USER)

    def invoke(self, args, from_tty):
        argv = gdb.string_to_argv(args)
        if len(argv) != 1:
            raise gdb.GdbError('argv error!')
        if args[-1] == '/':
            args += '../'
        else:
            args += '/../'
        fin_path = gdbDir_rec(args)
        fin_path = ':'.join(fin_path)
        gdb.execute('set dir ' + fin_path,True,True)
        print('glibc -> '+args)

class Gcinit(gdb.Command):
    def __init__(self):
        super(self.__class__, self).__init__("gcinit", gdb.COMMAND_USER)

    def invoke(self, args, from_tty):
        argv = gdb.string_to_argv(args)
        if len(argv)==1 and (argv[0]=='-h' or argv[0]=='--help'):
            print('-m32 version  --->  32 bit version of glibc ')
            print('-m64 version  --->  64 bit version of glibc ')
            print('-p path       --->  set path of glibc installed ')
        if len(argv) != 2:
            raise gdb.GdbError('argv error!')

        # 这里设置好放各版本glibc的总目录
        glibc_root = '/home/tover/Lab/gnu_c/glibc/glibc-'  # 这里把前缀顺便加上去了
        if argv[0]=='-m32':
            fin_path = glibc_root+argv[1]+'/x86/'
        elif argv[0]=='-m64':
            fin_path = glibc_root+argv[1]+'/x64/'
        elif argv[0]=='-p':
            fin_path = argv[1]
            if fin_path[-1] != '/':
                fin_path += '/'
        else:
            raise gdb.GdbError('input error!')
        print(fin_path)
        gdb.execute('set env LD_LIBRARY_PATH= '+fin_path+'lib/',True,True)
        gdb.execute('gcdir '+fin_path,True,True)

Gcdir()
Gcinit()

## example usage(in gdb):
#  gcdir /home/tover/Lab/glibc/glibc-2.23/x86
#
#  gcinit -m32 2.23
#  gcinit -p /home/tover/Lab/glibc/glibc-2.23/x86

用法就是在gdb中gcdir加安装的库的目录(自动递归遍历子目录)。另外还写了个设环境变量的,用法上面也有,这个需要改一下 glibc_root 换成自己对应的的glibc目录,而且目录结构是 "glibc_root(要改的那个)->各版本的glibc->安装目录(名字要是x64/x86,不是的话改一下脚本就好了)"。

脚本可以随便放一个文件,然后在 "/.gdbinit" 这个文件(没有的话新建一个)激活一下:

#这里换成脚本放的位置
    source /path/to/script

Ps:
经过我的测试,32位的可以正常运行,64位的话直接用gdb会有奇怪的错误,但是attach进去的话(要设LD_LIBRARY_PATH)可以正常使用(怎么attach的话google一下 - -)

这是结尾

其实有个叫 libc-dbg 的东西的🤔

    sudo apt-get install libc-dbg
    sudo apt-get install libc-dbg:i386
1 年 后

挖坟..XD
请问为什么编译之后得到的那些 .so 文件,比如 libm-2.31.so,我用 ldd 查看它的动态链接指向的是 /usr/lib 文件夹?
比如下面的例子,我的 /usr/lib/libc.so.6 是 2.32 版本,我感觉libc.so.6应该指向 /path/to/2.31/lib/libc.so.6

ldd libm-2.31.so 
	linux-vdso.so.1 (0x00007ffdb21dd000)
	libc.so.6 => /usr/lib/libc.so.6 (0x00007ff08b622000)
	/usr/lib64/ld-linux-x86-64.so.2 (0x00007ff08b928000)

    Allen_Hong
    某些 .so 文件是可以执行的(用来显示库的信息之类的),要顺利执行的话当然要链接当前系统的库了😅
    (不过刚试了一下好像我编出来的也执行不了,PS:链接指向这些是可以用某个编译参数改的)

    但是我还是有些疑惑,感觉会带来一些问题。
    比如本机的 glibc 为 2.32,编译的 glibc 为 2.31,编译后的 2.31 还是链接 2.32 当前的文件。举例来说,某个程序需要使用 libmvec-2.31,然后 libmvec-2.31 本身需要使用 libm.so,但是 libmvec-2.31.so 链接的是 系统中的 libm.so,也就是 libm-2.32.so,所以最后这个程序运行后加载的文件有 libmvec-2.31.so 和 libm-2.32.so,这样感觉会出问题哎。

    要解决我觉得可以通过设定编译后的 2.31 文件夹中的所有 libxxx.so 的 R_PATH 为 2.31 文件夹,这样比如找 libm.so 的时候会优先找 2.31 文件夹的文件。
    所以可以在 CFLAG/CXXFLAG 里面加入 -Wl,-rpth=/path/to/2.31/x64/lib(64bit 为例),不过我没还尝试过,可能不对XD

      Allen_Hong 举例来说,某个程序需要使用 libmvec-2.31,然后 libmvec-2.31 本身需要使用 libm.so

      但是程序链接一个库后这个库不会链接另外的库的啊😂

      © 2018-2025 0xFFFF