c++ 如何将llvm::outs()重定向到文件?

ddrv8njm  于 2023-06-25  发布在  其他
关注(0)|答案(3)|浏览(187)

我使用一些LLVM工具(如llvm-nm)作为静态库。也就是说,我复制了源代码llvm-nm.cpp,将main(..)重命名为llvm_nm(..),并将其编译为静态库。我想将标准输出转发到我的文件中。
我尝试使用下一种方法:

int    out_fd, err_fd;
  fpos_t out_pos, err_pos;

  // redirect out
  fflush(stdout);
  fgetpos(stdout, &out_pos);
  out_fd = dup(fileno(stdout));
  freopen(outFilename, "w", stdout);

  // execute
  int ret = llvm_nm(argc_, argv_);

  // restore output
  fflush(stdout);
  dup2(out_fd, fileno(stdout));
  close(out_fd);
  clearerr(stdout);
  fsetpos(stdout, &out_pos);

问题是它没有被转发(如果我在nm源代码中添加printf(),它可以工作,但对于nm输出则不行)。我查看了源代码,可以看到输出是使用llvm::outs()流完成的:

outs() << "Archive map" << "\n";

it's implemented的下一种方式:

/// outs() - This returns a reference to a raw_ostream for standard output.
00702 /// Use it like: outs() << "foo" << "bar";
00703 raw_ostream &llvm::outs() {
00704   // Set buffer settings to model stdout behavior.
00705   // Delete the file descriptor when the program exits, forcing error
00706   // detection. If you don't want this behavior, don't use outs().
00707   static raw_fd_ostream S(STDOUT_FILENO, true);
00708   return S;
00709 }

如何将输出重定向到我的文件?

brjng4g3

brjng4g31#

我意识到这是一个老问题,然而,我在查找llvm的outs()流的一些简单信息时遇到了这个问题,我找到了here
llvm“BrainF”附带的一个示例是这样使用的:

...

  raw_ostream *out = &outs();
  if (!JIT) {
    if (OutputFilename == "") {
      std::string base = InputFilename;
      if (InputFilename == "-") { base = "a"; }

      // Use default filename.
      OutputFilename = base+".bc";
    }
    if (OutputFilename != "-") {
      std::error_code EC;
      out = new raw_fd_ostream(OutputFilename, EC, sys::fs::F_None);
    }
  }

  ...

  if (out != &outs())
    delete out;

  ...

所以它似乎表明你可以安全地重新定向。

注意:在本例中,OutputFilename/InputFilename是使用llvm的Support Library CommandLine创建的std::string类型。

mefy6pfw

mefy6pfw2#

似乎没有一个简单的方法可以做到这一点:在llvm::raw_fd_ostreamllvm::raw_ostream中都没有复制/赋值构造函数。freopen技巧也不起作用,因为文件描述器整数用于初始化llvm::outs()返回的对象。
我看到的唯一方法是使用LD_PRELOAD动态地更改llvm::outs()的实现,或者类似的链接器技巧,但这对我来说听起来非常古怪。也许把原始符号标记为弱符号,然后在你的库中覆盖它?

6bc51xsx

6bc51xsx3#

如果你愿意容忍一些未定义的行为,你可以这样做:

#include <llvm/Support/raw_ostream.h>

static void hijack_log_line(const std::string &line) {
  // Do whatever logging you want here
  logInfo("[llvm] {}", line);
}

class hijack_raw_fd_ostream : public llvm::raw_fd_ostream {
public:
  explicit hijack_raw_fd_ostream()
    : llvm::raw_fd_ostream(-1, false, false) {
  }

protected:
  void write_impl(const char *Ptr, size_t Size) override {
    static std::string CurrentLine;

    std::string_view sv(Ptr, Ptr + Size);
    for (char ch : sv) {
      if (ch == '\n') {
        hijack_log_line(CurrentLine);
        CurrentLine.clear();
      } else {
        CurrentLine += ch;
      }
    }
  }

  uint64_t current_pos() const override {
    llvm::report_fatal_error("current_pos not implemented!");
  }

  size_t preferred_buffer_size() const override {
    return 0;
  }
} hijack_stream;
static_assert(sizeof(hijack_raw_fd_ostream) == sizeof(llvm::raw_fd_ostream));

int main(int argc, char **argv) {
  // Disable buffering in the LLVM streams
  llvm::outs().SetUnbuffered();
  llvm::errs().SetUnbuffered();

  // NOTE: This is technically undefined behaviour, but it works in practice
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdynamic-class-memaccess"
  std::memcpy(&llvm::outs(), &hijack_stream, sizeof(llvm::outs()));
  std::memcpy(&llvm::errs(), &hijack_stream, sizeof(llvm::errs()));
#pragma clang diagnostic pop

  llvm::outs() << "Hello ";
  llvm::outs() << "from LLVM!\n";
  return 0;
}

这是通过劫持vtable指针来挂钩write_implcurrent_pospreferred_buffer_size函数。这些行被累加并传递给hijack_log_line函数。
这取决于llvm::outs()在内部使用static raw_fd_ostream S;的实现细节。我们从这个类继承来确保内存布局是兼容的,但是如果这个实现改变,它可能会中断。

相关问题