欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Linux c 運(yùn)行時(shí)獲取動(dòng)態(tài)庫(kù)所在路徑的操作方法

 更新時(shí)間:2025年06月09日 09:22:34   作者:apocelipes  
運(yùn)行時(shí)獲取動(dòng)態(tài)庫(kù)地址除了dladdr和解析/proc/<pid>/maps還可以有一些別的做法,比如可以用nm獲取庫(kù)文件的符號(hào)表進(jìn)行對(duì)比,但如果庫(kù)文件被strip處理過(guò)就不能這么用了,本文介紹的兩種方案是泛用性最高的,感興趣的朋友一起看看吧

準(zhǔn)備

一般來(lái)說(shuō)動(dòng)態(tài)庫(kù)并不需要關(guān)心自己所在的文件系統(tǒng)上的路徑,但業(yè)務(wù)有那么多總有一兩個(gè)會(huì)有特殊需求。

現(xiàn)在給定一個(gè)動(dòng)態(tài)庫(kù)里的函數(shù)A,需求是要知道這個(gè)函數(shù)A是哪個(gè)動(dòng)態(tài)庫(kù)里的以及這個(gè)庫(kù)的存放路徑。

測(cè)試對(duì)象有兩個(gè),第一個(gè)是標(biāo)準(zhǔn)庫(kù)的函數(shù)printf,另一個(gè)是我們自己寫的動(dòng)態(tài)鏈接庫(kù)里的PrintRandomText函數(shù)。

自定義動(dòng)態(tài)庫(kù)的名字叫libmycustom1.so,代碼和編譯生成的庫(kù)都存放在libmycustom1目錄下。代碼如下:

// lib.h
#pragma once
#include <unistd.h>
#include <sys/random.h>
void PrintRandomText(ssize_t length);
// lib.c
#include <stdio.h>
#include "lib.h"
void PrintRandomText(ssize_t length)
{
    unsigned char buff[64] = {0};
    length = (length + 1) / 2;
    if (length == 0) {
        return;
    }
    while (1) {
        ssize_t count = getrandom(buff, 64, 0);
        count = length > count ? count : length;
        for (ssize_t i = 0; i < count; ++i) {
            printf("%02X", buff[i]&0xff);
        }
        if (length <= count) {
            break;
        }
        length -= count;
    }
    printf("\n");
}

函數(shù)很簡(jiǎn)單,從Linux的/dev/urandom隨機(jī)設(shè)備中讀取指定大小的數(shù)據(jù)然后打印輸出,編譯使用如下命令:

gcc -Wall -O2 -fPIC -shared lib.c -o libmycustom1.so

這樣我們就得到了libmycustom1/libmycustom1.so。下面可以介紹如何在運(yùn)行時(shí)獲取動(dòng)態(tài)庫(kù)的路徑了。

使用dladdr獲取動(dòng)態(tài)庫(kù)路徑

第一種方法是使用dladdr這個(gè)函數(shù)。dladdrlibdl.so中的一個(gè)函數(shù),用來(lái)獲取某個(gè)地址對(duì)應(yīng)的動(dòng)態(tài)庫(kù)信息,而libdl是Linux上專門用來(lái)處理動(dòng)態(tài)鏈接庫(kù)的函數(shù)庫(kù)。

dladdr獲取的信息中恰巧有動(dòng)態(tài)庫(kù)的實(shí)際存放路徑這一信息,我們可以加以利用:

#define _GNU_SOURCE // 這行不能少
#include <dlfcn.h>  // for dladdr
#include <stdio.h>
#include "libmycustom1/lib.h"
int main()
{
        Dl_info info1, info2;
        if (dladdr((void*)&printf, &info1) == 0) {
                fprintf(stderr, "cannot get printf's info\n");
                return 1;
        }
        if (dladdr((void*)&PrintRandomText, &info2) == 0) {
                fprintf(stderr, "cannot get PrintRandomText's info\n");
                return 1;
        }
        // 還需要檢查dli_fname字段是否是NULL,這里就省略了
        printf("lib contains printf: %s\n", info1.dli_fname);
        printf("lib contains PrintRandomText: %s\n", info2.dli_fname);
}

dladdr在出錯(cuò)的時(shí)候會(huì)返回0,這時(shí)可以用dlerror來(lái)獲取具體的報(bào)錯(cuò),不過(guò)這里我為了簡(jiǎn)單起見(jiàn)就省略了。

編譯運(yùn)行需要下面的命令:

$ gcc a.c -L./libmycustom1 -lmycustom1 -ldl
$ export LD_LIBRARY_PATH=./libmycustom1
$ ./a.out
lib contains printf: /lib/x86_64-linux-gnu/libc.so.6
lib contains PrintRandomText: ./libmycustom1/libmycustom1.so

編譯時(shí)還需要鏈接libdl。

因?yàn)閹?kù)沒(méi)有放在默認(rèn)的系統(tǒng)搜索路徑里,也沒(méi)有單獨(dú)設(shè)置ld.cache,因此我們需要設(shè)置環(huán)境變量LD_LIBRARY_PATH來(lái)告訴加載器我們的動(dòng)態(tài)庫(kù)在哪里。

可以看到對(duì)于存放在標(biāo)準(zhǔn)路徑里的libc,dladdr給出了絕對(duì)路徑,對(duì)于我們自定義的庫(kù),因?yàn)?code>LD_LIBRARY_PATH設(shè)置成了相對(duì)路徑,所以給我們的結(jié)果也是相對(duì)路徑的。因此dladdr拿到的結(jié)果最好得先做一次相對(duì)路徑到絕對(duì)路徑的轉(zhuǎn)換再使用。

dladdr受到廣泛的支持,基本主要的Linux發(fā)行版上都能使用,因此實(shí)際中大家也都在用它,但它還是有幾個(gè)缺點(diǎn):

  • 函數(shù)指針轉(zhuǎn)void*在c/c++標(biāo)準(zhǔn)中都是不允許的,而且實(shí)際也有函數(shù)指針是胖指針的平臺(tái)存在,但至少這一行為在x86_64和arm的gcc/clang上都沒(méi)啥問(wèn)題
  • dladdr只能正常獲取使用-fPIC編譯成位置不相關(guān)代碼的動(dòng)態(tài)庫(kù)信息,這個(gè)信息也不一定準(zhǔn)確。

綜上dladdr雖然能用,但不通用,而且可靠性也一般。

正如我在文章開(kāi)頭就說(shuō)了,這次討論的方案沒(méi)有可移植性,需要限定在具體的系統(tǒng)和硬件平臺(tái)上使用。

使用proc maps文件獲取動(dòng)態(tài)庫(kù)路徑

如果我不想再額外鏈接一個(gè)庫(kù),尤其是還得在文件開(kāi)頭定義#define _GNU_SOURCE,那么就需要使用方案二了。

方案二很簡(jiǎn)單也很直接,讀取進(jìn)程的/proc/<pid>/maps,對(duì)比地址范圍就能找到函數(shù)所在的動(dòng)態(tài)庫(kù)以及庫(kù)的路徑。

Linux加載動(dòng)態(tài)鏈接庫(kù)是用的類似mmap的形式,庫(kù)實(shí)際只會(huì)被加載一次,然后被映射到每個(gè)需要這個(gè)庫(kù)的進(jìn)程的地址空間里。

/proc/<pid>/maps記載了進(jìn)程的內(nèi)存地址空間里所有的mmap映射的文件,包括普通文件、共享庫(kù)和匿名映射。當(dāng)然這個(gè)文件里還包含了vdso和代碼段等的內(nèi)存地址,總體上來(lái)說(shuō)可以算作進(jìn)程的內(nèi)存空間分布概覽。一個(gè)例子是:

55bce8e1c000-55bce8e1d000 r--p 00000000 08:20 3337                       /home/apocelipes/dladdrtest/a.out
55bce8e1d000-55bce8e1e000 r-xp 00001000 08:20 3337                       /home/apocelipes/dladdrtest/a.out
55bce8e1e000-55bce8e1f000 r--p 00002000 08:20 3337                       /home/apocelipes/dladdrtest/a.out
55bce8e1f000-55bce8e20000 r--p 00002000 08:20 3337                       /home/apocelipes/dladdrtest/a.out
55bce8e20000-55bce8e21000 rw-p 00003000 08:20 3337                       /home/apocelipes/dladdrtest/a.out
55bd039bf000-55bd039e0000 rw-p 00000000 00:00 0                          [heap]
7f7bffb36000-7f7bffb39000 rw-p 00000000 00:00 0
7f7bffb39000-7f7bffb61000 r--p 00000000 08:20 49817                      /usr/lib/x86_64-linux-gnu/libc.so.6
7f7bffb61000-7f7bffce9000 r-xp 00028000 08:20 49817                      /usr/lib/x86_64-linux-gnu/libc.so.6
7f7bffce9000-7f7bffd38000 r--p 001b0000 08:20 49817                      /usr/lib/x86_64-linux-gnu/libc.so.6
7f7bffd38000-7f7bffd3c000 r--p 001fe000 08:20 49817                      /usr/lib/x86_64-linux-gnu/libc.so.6
7f7bffd3c000-7f7bffd3e000 rw-p 00202000 08:20 49817                      /usr/lib/x86_64-linux-gnu/libc.so.6
7f7bffd3e000-7f7bffd4b000 rw-p 00000000 00:00 0
7f7bffd53000-7f7bffd54000 r--p 00000000 08:20 3397                       /home/apocelipes/dladdrtest/libmycustom1/libmycustom1.so
7f7bffd54000-7f7bffd55000 r-xp 00001000 08:20 3397                       /home/apocelipes/dladdrtest/libmycustom1/libmycustom1.so
7f7bffd55000-7f7bffd56000 r--p 00002000 08:20 3397                       /home/apocelipes/dladdrtest/libmycustom1/libmycustom1.so
7f7bffd56000-7f7bffd57000 r--p 00002000 08:20 3397                       /home/apocelipes/dladdrtest/libmycustom1/libmycustom1.so
7f7bffd57000-7f7bffd58000 rw-p 00003000 08:20 3397                       /home/apocelipes/dladdrtest/libmycustom1/libmycustom1.so
7f7bffd58000-7f7bffd5a000 rw-p 00000000 00:00 0
7f7bffd5a000-7f7bffd5b000 r--p 00000000 08:20 49814                      /usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2
7f7bffd5b000-7f7bffd86000 r-xp 00001000 08:20 49814                      /usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2
7f7bffd86000-7f7bffd90000 r--p 0002c000 08:20 49814                      /usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2
7f7bffd90000-7f7bffd92000 r--p 00036000 08:20 49814                      /usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2
7f7bffd92000-7f7bffd94000 rw-p 00038000 08:20 49814                      /usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2
7fff6ded9000-7fff6defb000 rw-p 00000000 00:00 0                          [stack]
7fff6dfaa000-7fff6dfae000 r--p 00000000 00:00 0                          [vvar]
7fff6dfae000-7fff6dfb0000 r-xp 00000000 00:00 0                          [vdso]

可以看到libc和我們自己的庫(kù)都被記載進(jìn)文件里了。每行內(nèi)容是空格分開(kāi)的,對(duì)于匿名映射不會(huì)有最后的路徑。第一列的就是內(nèi)存地址,以“-”連字符分隔,第一部分是內(nèi)存映射區(qū)域開(kāi)始地址,第二部分是結(jié)束地址。

這和獲取函數(shù)對(duì)應(yīng)的動(dòng)態(tài)庫(kù)有什么關(guān)系呢?關(guān)系肯定是有的,在Linux上動(dòng)態(tài)庫(kù)里的“函數(shù)”其實(shí)就是一段編譯好的代碼,加載進(jìn)內(nèi)存后它也會(huì)占用一段內(nèi)存空間,調(diào)用動(dòng)態(tài)庫(kù)函數(shù)的時(shí)候?qū)嶋H上是下面這樣的流程:

  • 根據(jù)函數(shù)名稱跳轉(zhuǎn)到對(duì)應(yīng)的符號(hào)表項(xiàng)目上
  • 檢查函數(shù)是否被加載,有加載就跳過(guò)下面步驟直接到4
  • 未加載時(shí)loader會(huì)去動(dòng)態(tài)庫(kù)文件里讀取對(duì)應(yīng)函數(shù)的代碼,存入內(nèi)存,然后把項(xiàng)目?jī)?nèi)容用代碼在內(nèi)存里的起始地址覆蓋
  • 程序跳轉(zhuǎn)到函數(shù)代碼所在的內(nèi)存地址上,開(kāi)始一條條加載執(zhí)行這些代碼

加載進(jìn)內(nèi)存的代碼權(quán)限是r-xp,代表內(nèi)存里的內(nèi)容可以被執(zhí)行。

現(xiàn)在出于安全考慮有些程序會(huì)使用編譯選項(xiàng)把這些工作提前到程序加載運(yùn)行時(shí)就完成,但大致上是一樣的。被加載的函數(shù)的內(nèi)存會(huì)被記載進(jìn)maps文件,所以我們只要讀取maps文件然后對(duì)比內(nèi)存地址范圍,就能知道函數(shù)對(duì)應(yīng)的庫(kù)和路徑了。

因?yàn)槲覀冎豢春瘮?shù)地址,因此不用查的太細(xì),只要地址在范圍內(nèi)就可以,無(wú)需查看權(quán)限。知道原理后就可以寫個(gè)腳本去解析了:

local function searchAddr(pid, addr)
    local file = io.open("/proc/" .. pid .. "/maps", "r")
    if not file then
        print("進(jìn)程不存在: " .. pid)
        return
    end
    for line in file:lines() do
        local parts = {}
        for word in line:gmatch("%S+") do
            table.insert(parts, word)
        end
        if #parts > 5 then
            local addrParts = {}
            for addr in parts[1]:gmatch("[^%-]+") do
                table.insert(addrParts, addr)
            end
            if #addrParts == 2 then
                local startAddr = tonumber(addrParts[1], 16) or 0
                local endAddr = tonumber(addrParts[2], 16) or 0
                if startAddr <= addr and addr < endAddr then
                        print(parts[#parts])
                        break;
                end
            end
        end
    end
    file:close()
end
if #arg ~= 2 then
        print("no enough args")
        os.exit(1)
end
local addr = tonumber(arg[2]) or 0
if addr == 0 then
        print("addr can not be 0")
        os.exit(1)
end
searchAddr(arg[1], addr)

c語(yǔ)言處理字符串太折磨了,所以我用lua偷個(gè)懶,代碼就不解釋了因?yàn)楹芎?jiǎn)單,你可以讓ai代勞解讀一下。

進(jìn)程退出后proc文件也就沒(méi)了,所以測(cè)試代碼也得改一下不要讓進(jìn)程那么快退出:

#include <stdio.h>
#include "libmycustom1/lib.h"
int main()
{
    printf("pid %d\n", getpid());
    printf("printf address: %p\n", (void*)&printf);
    printf("PrintRandomText address: %p\n", (void*)&PrintRandomText);
    pause(); // 阻塞進(jìn)程直到收到信號(hào)
}

運(yùn)行結(jié)果:

可以看到我們順利找到了函數(shù)對(duì)應(yīng)的庫(kù)以及庫(kù)的存放路徑。

使用proc maps的優(yōu)點(diǎn)是不需要額外的依賴,而且得到的路徑都是絕對(duì)路徑。缺點(diǎn)則是需要函數(shù)指針轉(zhuǎn)換成地址值,以及proc是Linux等少數(shù)系統(tǒng)獨(dú)有的,不通用,而且讀取maps文件需要有專門的權(quán)限,這個(gè)權(quán)限默認(rèn)打開(kāi)但是可以選擇關(guān)閉。

總結(jié)

運(yùn)行時(shí)獲取動(dòng)態(tài)庫(kù)地址除了dladdr和解析/proc/<pid>/maps還可以有一些別的做法。比如可以用nm獲取庫(kù)文件的符號(hào)表進(jìn)行對(duì)比,但如果庫(kù)文件被strip處理過(guò)就不能這么用了。本文介紹的兩種方案是泛用性最高的。

另外也別太依賴這些結(jié)果,因?yàn)殡[藏或者篡改這些信息太過(guò)簡(jiǎn)單。如果你的想要?jiǎng)討B(tài)庫(kù)的路徑,應(yīng)該使用構(gòu)建系統(tǒng)注入信息或者干脆做出輸入選項(xiàng),而不是依靠這些可靠性和可移植性都欠佳的方案。

到此這篇關(guān)于Linux c 運(yùn)行時(shí)獲取動(dòng)態(tài)庫(kù)所在路徑 的文章就介紹到這了,更多相關(guān)Linux c 運(yùn)行動(dòng)態(tài)庫(kù)所在路徑 內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • 如何用C++制作LeetCode刷題小技巧-錯(cuò)題記錄本

    如何用C++制作LeetCode刷題小技巧-錯(cuò)題記錄本

    這篇文章主要介紹了如何用C++制作LeetCode刷題小技巧-錯(cuò)題記錄本的方法,需要的朋友可以參考下
    2021-04-04
  • 一文帶你學(xué)習(xí)C++析構(gòu)函數(shù)

    一文帶你學(xué)習(xí)C++析構(gòu)函數(shù)

    在C++中,析構(gòu)函數(shù)是一種特殊類型的成員函數(shù),用于在對(duì)象生命周期結(jié)束時(shí)被自動(dòng)調(diào)用,本文我們將介紹C++析構(gòu)函數(shù)的一些重要知識(shí)點(diǎn),并提供相應(yīng)代碼示例,需要的朋友可以參考下
    2023-05-05
  • boost.asio框架系列之調(diào)度器io_service

    boost.asio框架系列之調(diào)度器io_service

    這篇文章介紹了boost.asio框架系列之調(diào)度器io_service,對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2022-06-06
  • C++中單鏈表的建立與基本操作

    C++中單鏈表的建立與基本操作

    以下是對(duì)C++中單鏈表的建立與基本操作進(jìn)行了詳細(xì)的介紹,需要的朋友可以過(guò)來(lái)參考下,希望對(duì)大家有所幫助
    2013-10-10
  • C語(yǔ)言實(shí)現(xiàn)電器銷售管理系統(tǒng)

    C語(yǔ)言實(shí)現(xiàn)電器銷售管理系統(tǒng)

    這篇文章主要為大家詳細(xì)介紹了C語(yǔ)言實(shí)現(xiàn)電器銷售管理系統(tǒng),文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2022-06-06
  • C++?的cout格式化輸出場(chǎng)景示例詳解

    C++?的cout格式化輸出場(chǎng)景示例詳解

    這篇文章主要為大家介紹了C++的cout格式化輸出場(chǎng)景示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2023-09-09
  • 使用C語(yǔ)言構(gòu)建基本的二叉樹(shù)數(shù)據(jù)結(jié)構(gòu)

    使用C語(yǔ)言構(gòu)建基本的二叉樹(shù)數(shù)據(jù)結(jié)構(gòu)

    這篇文章主要介紹了使用C語(yǔ)言使用C語(yǔ)言構(gòu)建基本的二叉樹(shù)數(shù)據(jù)結(jié)構(gòu),包括根據(jù)前序序列和中序序列構(gòu)建二叉樹(shù)的方法,需要的朋友可以參考下
    2015-08-08
  • C語(yǔ)言實(shí)現(xiàn)繪制余弦曲線

    C語(yǔ)言實(shí)現(xiàn)繪制余弦曲線

    這篇文章主要為大家詳細(xì)介紹了C語(yǔ)言實(shí)現(xiàn)繪制余弦曲線的相關(guān)知識(shí),文中的示例代碼講解詳細(xì),感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下
    2024-01-01
  • C++經(jīng)典例題之字符串特定規(guī)則反轉(zhuǎn)問(wèn)題的解法

    C++經(jīng)典例題之字符串特定規(guī)則反轉(zhuǎn)問(wèn)題的解法

    這篇文章主要介紹了如何解決字符串反轉(zhuǎn)問(wèn)題,通過(guò)將字符串按每2k個(gè)字符為一個(gè)區(qū)間進(jìn)行劃分,并使用雙指針?lè)椒▉?lái)確定實(shí)際反轉(zhuǎn)的邊界,最終實(shí)現(xiàn)字符串按特定規(guī)則進(jìn)行反轉(zhuǎn),文中通過(guò)代碼介紹的非常詳細(xì),需要的朋友可以參考下
    2025-03-03
  • C++使struct對(duì)象擁有可變大小的數(shù)組(詳解)

    C++使struct對(duì)象擁有可變大小的數(shù)組(詳解)

    下面小編就為大家?guī)?lái)一篇C++使struct對(duì)象擁有可變大小的數(shù)組(詳解)。小編覺(jué)得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧
    2016-12-12

最新評(píng)論