Refactor DisassemblerObjdump.

This change removes ScopedTmpFile from DisassemblerObjdump, and
replaces it with the use of the implementation in linux/common.

It also switches to using ScopedPipe to read the output from objdump,
and directly execing objdump instead of using system.

Change-Id: I6d425190fb4a20d6b265c72aa7315026687cb86a
Reviewed-on: https://chromium-review.googlesource.com/c/breakpad/breakpad/+/3971919
Reviewed-by: Ivan Penkov <ivanpe@chromium.org>
This commit is contained in:
Mark Brand 2023-01-30 11:33:54 +01:00 committed by Ivan Penkov
parent 5daa41904a
commit 38115b0c5f
3 changed files with 178 additions and 137 deletions

View file

@ -33,6 +33,9 @@
#include "processor/disassembler_objdump.h"
#include <unistd.h>
#include <sys/wait.h>
#include <array>
#include <fstream>
#include <iostream>
#include <iterator>
@ -40,77 +43,16 @@
#include <sstream>
#include <vector>
#include "common/linux/eintr_wrapper.h"
#include "common/linux/scoped_pipe.h"
#include "common/linux/scoped_tmpfile.h"
#include "processor/logging.h"
namespace google_breakpad {
namespace {
const size_t kMaxX86InstructionLength = 15;
// Small RAII wrapper for temporary files.
//
// Example:
// ScopedTmpFile tmp("/tmp/tmpfile-XXXX");
// if (tmp.Create()) {
// std::cerr << tmp.path() << std::endl;
// }
class ScopedTmpFile {
public:
// Initialize the ScopedTmpFile object - this does not create the temporary
// file yet.
ScopedTmpFile(const char* path_format);
~ScopedTmpFile();
// Creates the temporary file, returns true on success.
bool Create();
// Writes bytes to the temporary file, returns true on success.
bool Write(const uint8_t* bytes, unsigned int bytes_len);
// Returns the path of the temporary file.
string path() const { return path_; }
private:
int fd_;
string path_;
};
ScopedTmpFile::ScopedTmpFile(const char* path_format) : path_(path_format) {}
ScopedTmpFile::~ScopedTmpFile() {
if (fd_) {
close(fd_);
unlink(path_.c_str());
}
}
bool ScopedTmpFile::Create() {
fd_ = mkstemp(path_.data());
if (fd_ < 0) {
unlink(path_.c_str());
fd_ = 0;
path_ = "";
return false;
}
return true;
}
bool ScopedTmpFile::Write(const uint8_t* bytes, unsigned int bytes_len) {
if (fd_) {
do {
ssize_t result = write(fd_, bytes, bytes_len);
if (result < 0) {
break;
}
bytes += result;
bytes_len -= result;
} while (bytes_len);
}
return bytes_len == 0;
}
bool IsInstructionPrefix(const string& token) {
if (token == "lock" || token == "rep" || token == "repz" ||
token == "repnz") {
@ -284,47 +226,87 @@ bool DisassemblerObjdump::DisassembleInstruction(uint32_t cpu,
return false;
}
// Create two temporary files, one for the raw instruction bytes to pass to
// objdump, and one for the output, and write the bytes to the input file.
ScopedTmpFile raw_bytes_file("/tmp/breakpad_mem_region-raw_bytes-XXXXXX");
ScopedTmpFile disassembly_file("/tmp/breakpad_mem_region-disassembly-XXXXXX");
if (!raw_bytes_file.Create() || !disassembly_file.Create() ||
!raw_bytes_file.Write(raw_bytes, raw_bytes_len)) {
BPLOG(ERROR) << "Failed creating temporary files.";
// Create a temporary file for the raw instruction bytes to pass to
// objdump, and write the bytes to the input file.
ScopedTmpFile raw_bytes_file;
if (!raw_bytes_file.InitData(raw_bytes, raw_bytes_len)) {
BPLOG(ERROR) << "Failed creating temporary file.";
return false;
}
char cmd[1024] = {0};
snprintf(cmd, 1024,
"objdump -D --no-show-raw-insn -b binary -M intel -m %s %s > %s",
architecture.c_str(), raw_bytes_file.path().c_str(),
disassembly_file.path().c_str());
if (system(cmd)) {
BPLOG(ERROR) << "Failed to call objdump.";
// Create a pipe to use to read the disassembly back from objdump.
ScopedPipe disassembly_pipe;
if (!disassembly_pipe.Init()) {
BPLOG(ERROR) << "Failed creating pipe for output.";
return false;
}
// Pipe each output line into the string until the string contains the first
// instruction from objdump.
std::ifstream objdump_stream(disassembly_file.path());
pid_t child_pid = fork();
if (child_pid < 0) {
BPLOG(ERROR) << "Fork failed.";
return false;
}
// Match the instruction line, from:
// 0: lock cmpxchg DWORD PTR [esi+0x10],eax
// extract the string "lock cmpxchg DWORD PTR [esi+0x10],eax"
std::regex instruction_regex(
"^\\s+[0-9a-f]+:\\s+" // " 0:"
"((?:\\s*\\S*)+)$"); // "lock cmpxchg..."
if (child_pid == 0) {
// In the child process, set up the input and output file descriptors.
if (dup2(raw_bytes_file.GetFd(), STDIN_FILENO) < 0 ||
disassembly_pipe.Dup2WriteFd(STDOUT_FILENO) < 0 ||
disassembly_pipe.Dup2WriteFd(STDERR_FILENO) < 0) {
BPLOG(ERROR) << "Failed dup'ing file descriptors.";
exit(-1);
}
std::string line;
std::smatch match;
do {
if (!getline(objdump_stream, line)) {
BPLOG(INFO) << "Failed to find instruction in objdump output.";
// We need to close the read end of the pipe in the child process so that
// when the parent closes it, the pipe is disconnected.
disassembly_pipe.CloseReadFd();
// We use "/proc/self/fd/0" here to allow objdump to parse an unnamed file,
// since objdump does not have a mode to read from stdin. This cannot be
// used with a pipe, since objdump requires that the input is a standard
// file.
execlp("objdump", "objdump", "-D", "--no-show-raw-insn", "-b", "binary",
"-M", "intel", "-m", architecture.c_str(), "/proc/self/fd/0",
nullptr);
BPLOG(ERROR) << "Failed to exec objdump.";
exit(-1);
} else {
// In the parent process, parse the objdump output.
// Match the instruction line, from:
// 0: lock cmpxchg DWORD PTR [esi+0x10],eax
// extract the string "lock cmpxchg DWORD PTR [esi+0x10],eax"
std::regex instruction_regex(
"^\\s+[0-9a-f]+:\\s+" // " 0:"
"((?:\\s*\\S*)+)$"); // "lock cmpxchg..."
std::string line;
std::smatch match;
while (disassembly_pipe.ReadLine(line)) {
if (std::regex_match(line, match, instruction_regex)) {
instruction = match[1].str();
break;
}
}
// Close the read pipe so that objdump will exit (in case we broke out of
// the loop above before reading all of the output).
disassembly_pipe.CloseReadFd();
// Now wait for objdump to exit.
int status = 0;
HANDLE_EINTR(waitpid(child_pid, &status, 0));
if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) {
BPLOG(ERROR) << "objdump didn't run successfully.";
return false;
}
} while (!std::regex_match(line, match, instruction_regex));
instruction = match[1].str();
if (instruction == "") {
BPLOG(ERROR) << "Failed to find instruction in objdump output.";
return false;
}
}
return true;
}