How to redirect llvm::outs() to file?
Asked Answered
H

3

11

I'm using some LLVM tools (like llvm-nm) as static libraries. I.e. i copied source llvm-nm.cpp, renamed main(..) to llvm_nm(..) and compiled it as static library. I'd like to forward standard output to my file.

I've tried to use the next approach:

  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); 

The problem is that it's not forwarded (it works if i add printf() in nm source code but not for nm output). I've looke to the source and i can see output is done using llvm::outs() stream:

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

And it's implemented the next way:

/// 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 }

How can i redirect that output to my file?

Highjack answered 11/10, 2014 at 17:14 Comment(0)
M
7

I realize this is an old question, however, I came across it looking up some simple information for llvm's outs() stream which I found here.

One of the examples that comes with llvm "BrainF" uses it like this:

  ...

  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;

  ...

So it seems to suggest that you are safe to redirect.

Note: in this example OutputFilename/InputFilename are std::string types created with llvm's Support Library CommandLine.

Missioner answered 7/12, 2015 at 19:8 Comment(0)
R
2

There does not seem to be an easy way to do so: there's no copy / assignment constructor in llvm::raw_fd_ostream nor in llvm::raw_ostream. And the freopen trick does not work either because the file descritor integer is used to initialize the object returned by llvm::outs().

The only way I see is to use LD_PRELOAD to change the implementation of llvm::outs() on the fly, or similar linker tricks, but that sounds very hacky to me. Maybe mark the original symbol as weak and then override it in your library?

Road answered 31/8, 2015 at 13:15 Comment(0)
P
0

If you are willing to tolerate some undefined behaviour you can do something like this:

#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;
}

This works by hijacking the vtable pointer to hook the write_impl, current_pos and preferred_buffer_size functions. The lines are accumulated and passed to the hijack_log_line function.

It depends on the implementation detail that llvm::outs() uses a static raw_fd_ostream S; internally. We inherit from this class to make sure the memory layout is compatible, but it can break if this implementation changes.

Premaxilla answered 19/6, 2023 at 14:55 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.