プログラムのopenatによって開かれたパスを変更するには、LD_PRELOADを使用します。

プログラムのopenatによって開かれたパスを変更するには、LD_PRELOADを使用します。

プログラムが実際にファイルシステムで開くいくつかのパスにパスを変更したいと思います。その理由は、プログラムを並列に実行したいのですが、そのプログラムはそれを一時ディレクトリ/tmp/somedir/として使用し、並列インスタンスで競合が発生するためです。

私はトリックを実行する素晴らしい答えを見つけました。プロセスの特定のパスを偽造することは可能ですか?。残念ながら、これはcat広告には機能しますが、私のプログラムには機能しません。その理由は、プログラムがC ++ APIを使用しているためだと思います。

再現するために、最初にファイルにいくつかのコンテンツを書き込む非常に簡単なプログラムを作成しました。

#include <fstream>
#include <string_view>
#include <iostream>

int main() {
    std::ofstream myfile;
    myfile.open("test.log");
    std::string_view text{"hello world\n"};
    myfile.write(text.data(), text.size());
    return 0;
}

straceそれから最後にこれを使ってみました。

brk(NULL)                               = 0x558b5d5e3000
brk(0x558b5d604000)                     = 0x558b5d604000
futex(0x7f94e2e7e77c, FUTEX_WAKE_PRIVATE, 2147483647) = 0
openat(AT_FDCWD, "test.log", O_WRONLY|O_CREAT|O_TRUNC, 0666) = 3
write(3, "hello world\n", 12)           = 12
close(3)                                = 0
exit_group(0)                           = ?

したがって、C APIを呼び出したように見えるために使用された関数はopenat

私はC soライブラリについてもこれを見ました。これについては後で取り上げます。

openat(AT_FDCWD, "/lib/x86_64-linux-gnu/libc.so.6", O_RDONLY|O_CLOEXEC) = 3

だから私はopenat実装にさらにopen実装しました。ここに完全なプログラムがあります。テスト目的でパスを変更せずにファイルに書き込みました。

/*
 * capture calls to a routine and replace with your code
 * g++ -Wall -O2 -fpic -shared -ldl -lstdc++ -o fake_open_file.so fake_open_file.cpp
 * LD_PRELOAD=/home/myname/fake_open_file.so cat
 */
#define _FCNTL_H 1 /* hack for open() prototype */
#undef _GNU_SOURCE
#define _GNU_SOURCE /* needed to get RTLD_NEXT defined in dlfcn.h */
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <dlfcn.h>
#include <mutex>
#include <fstream>
#include <string_view>
#include <iostream>


// for the test, I just log anything that was open into a new log file
struct open_reporter
{
    open_reporter() = default;
    void report_filename(std::string_view filename)
    {
        std::lock_guard<std::mutex> l{file_report_mutex};
        if(!is_open) {
            myfile.open("/home/myname/fileopen.log");
        }
        std::string tmp = std::string{filename} + "\n";
        myfile.write(tmp.data(), tmp.size());
    }
    std::ofstream myfile;
    std::mutex file_report_mutex;
    bool is_open = false;
};

static open_reporter reporter_;

extern "C" {
    int open(const char *pathname, int flags, mode_t mode)
    {
        static int (*real_open)(const char *pathname, int flags, mode_t mode) = nullptr;

        if (!real_open) {
            real_open = reinterpret_cast<decltype(real_open)>(dlsym(RTLD_NEXT, "open"));
            char *error = dlerror();
            if (error != nullptr) {
                reporter_.report_filename("ERROR OCCURED!");
                reporter_.report_filename(error);
                exit(1);
            }
        }

        reporter_.report_filename(pathname);
        return real_open(pathname, flags, mode);
    }

    int openat(int dirfd, const char *pathname, int flags, mode_t mode)
    {
        static int (*real_openat)(int dirfd, const char *pathname, int flags, mode_t mode) = nullptr;

        if (!real_openat) {
            real_openat = reinterpret_cast<decltype(real_openat)>(dlsym(RTLD_NEXT, "openat"));
            char *error = dlerror();
            if (error != nullptr) {
                reporter_.report_filename("ERROR OCCURED!");
                reporter_.report_filename(error);
                exit(1);
            }
        }

        reporter_.report_filename(pathname);
        return real_openat(dirfd, pathname, flags, mode);
    }
}

これはcat静けさでは動作しますが、私のテストプログラムでは動作しません。 0を変更しopenopenat返してもこれが中断されても、cat私のテストプログラムには影響しません。また、次の記号が私のバイナリにあることを確認しました。

$ nm -gD fake_open_file.so | grep open
0000000000001470 W _ZN13open_reporterD1Ev
0000000000001470 W _ZN13open_reporterD2Ev
0000000000001450 T open
0000000000001460 T openat

両方の機能が存在することがわかります。 Cライブラリを見ると違いが見えますが、どういう意味なのかわかりません。openまたは、次以外のものを編集しましたopenat

$ nm -gD /lib/x86_64-linux-gnu/libc.so.6 |grep open
0000000000114820 W openat@@GLIBC_2.4
0000000000114820 W openat64@@GLIBC_2.4

0000000000114690 W open@@GLIBC_2.2.5
0000000000114690 W open64@@GLIBC_2.2.5

0000000000114690 W __open@@GLIBC_2.2.5
0000000000114690 W __open64@@GLIBC_2.2.5
00000000001147c0 T __open64_2@@GLIBC_2.7
0000000000119b80 T __open64_nocancel@@GLIBC_PRIVATE
0000000000114660 T __open_2@@GLIBC_2.7
0000000000040800 T __open_catalog@@GLIBC_PRIVATE
0000000000119b80 T __open_nocancel@@GLIBC_PRIVATE
0000000000114950 T __openat64_2@@GLIBC_2.7
00000000001147f0 T __openat_2@@GLIBC_2.7

内容以外は@@GLIBC同じです。私はこれを一度も試したことがないので、それは私のデバッグ能力です。私が元の答えを得たところがここにあるので、このように尋ねるのではなく、ここに質問することです。これはプログラミングの質問ではなく、Linuxの知識のように見え、プログラム自体は非常に簡単です。

答え1

背景:C

出力strace結果は次のとおりです。

brk(NULL)                               = 0x558b5d5e3000
brk(0x558b5d604000)                     = 0x558b5d604000
futex(0x7f94e2e7e77c, FUTEX_WAKE_PRIVATE, 2147483647) = 0
openat(AT_FDCWD, "test.log", O_WRONLY|O_CREAT|O_TRUNC, 0666) = 3
write(3, "hello world\n", 12)           = 12
close(3)                                = 0
exit_group(0)                           = ?

...しかし、strace関数呼び出しではなくシステム呼び出しを追跡し、...使用されたLD_PRELOAD関数を介して挿入します。機能着信電話。 Cプログラムの場合または経由でopenat呼び出すことができます。たとえば、次のように起動した場合:openopen64

#include <stdlib.h>
#include <unistd.h>
#include <stdio.h>
#include <fcntl.h>

int main() {
    int fd;
    int nb;

    if (-1 == (fd = open("test.log", O_RDWR|O_CREAT, 0666))) {
        perror("open");
        exit(1);
    }

    if (-1 == (nb = write(fd, "hello world\n", 12))) {
        perror("write");
        exit(1);
    }

    printf("write %d bytes\n", nb);

    return 0;
}

次の出力でこれを見ることができますstrace

openat(AT_FDCWD, "test.log", O_RDWR|O_CREAT, 0666) = 3

しかし、あなたと同じオーバーライドを使用しようとすると、同じ動作が表示されます。動作しませんopenatLD_PRELOADただし、通話を傍受すると、次のようになりますopen

int open(const char *pathname, int flags, mode_t mode) {
    static int (*real_open)(const char *, int, mode_t);

    fprintf(stderr, "OPEN PATH: %s\n", pathname);

    if (!real_open) {
        real_open = (dlsym(RTLD_NEXT, "open"));
        char *error = dlerror();
        if (error != NULL) {
            fprintf(stderr, "ERROR OCCURED! %s\n", error);
            exit(1);
        }
    }

    return real_open(pathname, flags, mode);
}

それから素晴らしい仕事をします。

$ LD_PRELOAD=./fakeopen.so ./c_example
OPEN PATH: test.log
write 12 bytes

より複雑さ:C++

C ++コードを使用すると、状況が少し複雑になります。なぜなら書くとき...

myfile.open("test.log");

...正確に何と呼びますか?出力を見るとLD_DEBUG=symbols ./your_program以下が表示されます。

    420239:     symbol=_ZNSt14basic_ofstreamIcSt11char_traitsIcEE4openEPKcSt13_Ios_Openmode;  lookup in file=./cc_main [0]
    420239:     symbol=_ZNSt14basic_ofstreamIcSt11char_traitsIcEE4openEPKcSt13_Ios_Openmode;  lookup in file=/lib64/libstdc++.so.6 [0]

したがって、実際の関数呼び出しは次のようになります。破損した。と同じ名前を使用すると、他の関数と同じよう_ZNSt14basic_ofstreamIcSt11char_traitsIcEE4openEPKcSt13_Ios_Openmodeに上書きできます。私たちが作成した場合wrapper.cc

#include <iostream>

extern "C" {
    int _ZNSt14basic_ofstreamIcSt11char_traitsIcEE4openEPKcSt13_Ios_Openmode() {
        {
            std::cerr << "This is the wrapped open method\n";
            return 0;
        }
    }
}

次のようにコンパイルしますwrapper.so

g++ -shared -fPIC -o wrapper.so wrapper.cc

その後、簡単なプログラムでそれを使用できます。

LD_PRELOAD=./wrapper.so ./your_program

出力を取得します。

$ LD_PRELOAD=./wrapper.so ./your_program
This is the wrapped open method

こうしてopenメソッドを正常にラップしました!実際のメソッドを正常に呼び出すには、_ZNSt14basic_ofstreamIcSt11char_traitsIcEE4openEPKcSt13_Ios_Openmode関数署名が実際にどのように見えるかを把握する必要があります。私はC ++の専門家ではないので、この質問に答えることはできません。しかし、これがあなたの発展に役立つことを願っています。

その他の注意事項

関数のオーバーロードはC ++を使用して実行できます。これにより、間違った名前の代わりに一般的な関数名を使用できます(そして、C ++関数のCプロトタイプがどのように見えるかを推測する必要はありません)。

これについては、以下でやや議論します。stackoverflowに関するこの質問

関連情報