프로그램의 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을 변경 open하고 openat반환하더라도 이것이 중단되더라도 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

그러나 당신과 같은 재정의를 사용하려고 하면 동일한 동작이 나타납니다. 작동하지 않습니다 openat. LD_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에 대한 이 질문.

관련 정보