forked from nodejs/node
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
postmortem host and lldb plugin prototypes
Showing
12 changed files
with
761 additions
and
7 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,86 @@ | ||
#!/usr/bin/python | ||
|
||
import lldb | ||
import argparse | ||
from functools import wraps | ||
|
||
from .helpers import validate_hex | ||
from .postmortem_host import PostmortemHost | ||
|
||
|
||
def v8_load(debugger, args, result, internal_dict): | ||
debugger.SetAsync(True) | ||
|
||
postmortem_host = PostmortemHost(debugger) | ||
if postmortem_host.is_valid: | ||
# TODO support multiple targets | ||
internal_dict["v8_postmortem_host"] = postmortem_host | ||
|
||
return True | ||
|
||
|
||
def v8_postmortem_host(func): | ||
@wraps(func) | ||
def v8_postmortem_host_wrapper(debugger, args, result, internal_dict): | ||
if not "v8_postmortem_host" in internal_dict: | ||
if not v8_load(debugger, args, result, internal_dict): | ||
return False | ||
postmortem_host = internal_dict["v8_postmortem_host"] | ||
return func(debugger, args, result, internal_dict, postmortem_host) | ||
return v8_postmortem_host_wrapper | ||
|
||
|
||
@v8_postmortem_host | ||
def v8_stack(debugger, args, result, internal_dict, postmortem_host): | ||
core_target = debugger.GetSelectedTarget() | ||
core_process = core_target.process | ||
thread = core_process.selected_thread | ||
print thread.frame[0] | ||
frame = thread.frame[0] | ||
|
||
stack_pointer = frame.FindRegister("rsp").unsigned | ||
program_counter = frame.FindRegister("rip").unsigned | ||
postmortem_host.send("s %d %d" % (stack_pointer, program_counter)) | ||
|
||
stack = postmortem_host.listen() | ||
if stack: | ||
print stack | ||
return True | ||
return False | ||
|
||
|
||
@v8_postmortem_host | ||
def v8_print(debugger, args, result, internal_dict, postmortem_host): | ||
postmortem_host.send("p %x" % args.address) | ||
|
||
obj = postmortem_host.listen() | ||
if obj: | ||
print obj | ||
return True | ||
return False | ||
|
||
|
||
|
||
def v8_handler(debugger, command, result, internal_dict): | ||
v8_parser = argparse.ArgumentParser(prog='v8', description='v8-related commands') | ||
subparsers = v8_parser.add_subparsers() | ||
|
||
load_parser = subparsers.add_parser('load') | ||
load_parser.set_defaults(func=v8_load) | ||
|
||
stack_parser = subparsers.add_parser('stack') | ||
stack_parser.set_defaults(func=v8_stack) | ||
|
||
print_parser = subparsers.add_parser('print') | ||
print_parser.add_argument("address", type=validate_hex) | ||
print_parser.set_defaults(func=v8_print) | ||
|
||
args = v8_parser.parse_args(command.split(" ")) | ||
args.func(debugger, args, result, internal_dict) | ||
|
||
return True | ||
|
||
|
||
# And the initialization code to add your commands | ||
def __lldb_init_module(debugger, internal_dict): | ||
debugger.HandleCommand('command script add -f lldb_v8.v8_handler v8') |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
import re | ||
import argparse | ||
|
||
|
||
# TODO(mmarchini) use Python logger? | ||
def logger(*args): | ||
args = map(str, args) | ||
print "[lldb_v8] ", " ".join(args) | ||
|
||
|
||
def int_to_buffer(value): | ||
value = hex(value)[2:] | ||
|
||
# Fill zero in the left in hex has odd length | ||
value = value.rjust((len(value)/2 + 1) * 2, '0') | ||
|
||
# Split hex value into bytes (two hex digits) | ||
value_bytes = list(map(''.join, zip(*[iter(value)]*2))) | ||
|
||
# Convert bytes into chars and return | ||
|
||
return "".join([chr(int(v, 16)) for v in value_bytes]) | ||
|
||
|
||
HEX_RE = re.compile(r"^(?:0x){0,1}[0-9a-f]+") | ||
def validate_hex(val): | ||
match = HEX_RE.match(val) | ||
if not match: | ||
raise argparse.ArgumentTypeError | ||
return int(val, 16) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,287 @@ | ||
import os | ||
from time import sleep | ||
import tempfile | ||
from os import path | ||
from functools import wraps | ||
from collections import OrderedDict | ||
|
||
import lldb | ||
|
||
from .helpers import int_to_buffer, logger | ||
from .tls import TLSAccessor | ||
|
||
def ipc_call(ipc_method, ipc_args): | ||
def ipc_call_decorator(func): | ||
@wraps(func) | ||
def ipc_call_wrapper(*args): | ||
return func(args[0], *[ipc_args[i](args[i + 1]) for i in range(len(ipc_args))]) | ||
ipc_call_wrapper._ipc_method = ipc_method | ||
return ipc_call_wrapper | ||
return ipc_call_decorator | ||
|
||
|
||
class MemoryContent(object): | ||
|
||
def __init__(self): | ||
self.content = bytes() | ||
self.addr = 0 | ||
self.len = 0 | ||
|
||
|
||
class PostmortemHost(object): | ||
def __init__(self, debugger): | ||
self.debugger = debugger | ||
self.is_valid = False | ||
self.create_ipc_files() | ||
if self.create_host_process(): | ||
self.is_valid = self.load_core_memory_on_host_process() | ||
|
||
@property | ||
def ipc_methods(self): | ||
ipc_methods = {} | ||
for name in dir(self): | ||
if name == "ipc_methods": | ||
continue | ||
attr = getattr(self, name, None) | ||
if hasattr(attr, "_ipc_method"): | ||
ipc_methods[attr._ipc_method] = attr | ||
return ipc_methods | ||
|
||
def create_host_process(self): | ||
# TODO add some checks | ||
self.core_target = self.debugger.GetSelectedTarget() | ||
self.core_process = self.core_target.process | ||
|
||
self.host_target = self.debugger.CreateTarget(self.core_target.executable.fullpath) | ||
|
||
error = lldb.SBError() | ||
launch_parameters = OrderedDict([ | ||
("listener", self.debugger.GetListener()), | ||
("argv", | ||
["--experimental-postmortem-host", self.stdin.name, self.stdout.name]), | ||
("envp", None), | ||
("stdin_path", "/dev/null"), | ||
("stdout_path", "/dev/null"), | ||
("stderr_path", "/tmp/stderr"), | ||
("working_directory", None), | ||
("launch_flags", 0), | ||
("stop_at_entry", False), | ||
("error", error), | ||
]) | ||
self.host_process = self.host_target.Launch(*(launch_parameters.values())) | ||
|
||
if error.Fail() or not self.host_process.IsValid(): | ||
logger(error) | ||
return False | ||
|
||
retries = 5 | ||
while not self.host_process.is_running: | ||
if retries == 0: | ||
logger("Host process is not running") | ||
return False | ||
retries -= 1 | ||
sleep(1) | ||
|
||
return True | ||
|
||
def should_load_memory(self, memory_info): | ||
# TODO (mmarchini) also copy executable sections | ||
if not (memory_info.IsReadable() or memory_info.IsExecutable()): | ||
return False | ||
|
||
start = memory_info.GetRegionBase() | ||
end = memory_info.GetRegionEnd() | ||
|
||
return True | ||
|
||
def get_memory_content(self, memory_info): | ||
error = lldb.SBError() | ||
|
||
memory_content = MemoryContent() | ||
|
||
memory_content.addr = memory_info.GetRegionBase() | ||
memory_content.len = memory_info.GetRegionEnd() - memory_content.addr | ||
|
||
process = self.core_process | ||
read_addr = memory_content.addr | ||
|
||
process_stopped = False | ||
if memory_info.IsExecutable(): | ||
for section in sum([m.sections for m in self.core_target.modules], []): | ||
sec_start = section.GetLoadAddress(self.core_target) | ||
sec_end = sec_start + section.size | ||
|
||
if (sec_start <= read_addr and read_addr <= sec_end): | ||
self.host_process.Stop() | ||
process_stopped = True | ||
offset = read_addr - sec_start | ||
read_addr = section.GetLoadAddress(self.host_target) + offset | ||
process = self.host_process | ||
break | ||
|
||
|
||
memory_content.content = bytes(process.ReadMemory(read_addr, memory_content.len, error)) | ||
if process_stopped: | ||
self.host_process.Continue() | ||
retries = 5 | ||
while not self.host_process.is_running: | ||
if retries == 0: | ||
logger("Host process is not running") | ||
raise Exception | ||
retries -= 1 | ||
sleep(1) | ||
|
||
if error.Fail(): | ||
logger(error) | ||
return None | ||
if len(memory_content.content) != memory_content.len: | ||
logger("Wrong read size") | ||
return None | ||
|
||
return memory_content | ||
|
||
def load_core_memory_on_host_process(self): | ||
error = lldb.SBError() | ||
memory_ranges = self.core_process.GetMemoryRegions() | ||
memory_info = lldb.SBMemoryRegionInfo() | ||
|
||
regions = 0 | ||
failures = 0 | ||
|
||
for i in range(memory_ranges.GetSize()): | ||
if not memory_ranges.GetMemoryRegionAtIndex(i, memory_info): | ||
logger("Range unavailable: ", i) | ||
|
||
# TODO (mmarchini) also copy executable sections | ||
if not self.should_load_memory(memory_info): | ||
continue | ||
|
||
regions += 1 | ||
|
||
memory_content = self.get_memory_content(memory_info) | ||
if not memory_content: | ||
continue | ||
|
||
filename = path.join(self._tmpdir, "%x" % memory_content.addr) | ||
|
||
# TODO store file path in context | ||
with open(filename, "wb+") as f: | ||
f.write(memory_content.content) | ||
f.flush() | ||
os.chmod(filename, 0744) | ||
|
||
message = "%x %d %s" % (memory_content.addr, memory_content.len, filename) | ||
self.send(message) | ||
|
||
ret = int(self.receive()) | ||
if ret != memory_content.len: | ||
failures += 1 | ||
logger("Couldn't write region [0x%x, 0x%x)" % (memory_content.addr, memory_content.addr + memory_content.len)) | ||
logger("Wrong size written: %s != %s" % (memory_content.len, ret)) | ||
os.remove(filename) | ||
|
||
self.send("done") | ||
|
||
self.debugger.SetSelectedTarget(self.core_target) | ||
|
||
print "%d Memory Regions loaded (%d Failed)" % (regions, failures) | ||
return True | ||
|
||
def create_ipc_files(self): | ||
self._tmpdir = tempfile.mkdtemp() | ||
|
||
self.stdin = open(path.join(self._tmpdir, "stdin"), "wb+") | ||
|
||
with open(path.join(self._tmpdir, "stdout"), "w+") as f: | ||
self.stdout = open(f.name, "r") | ||
self.current_line = 0 | ||
self._stdout_buffer = [] | ||
|
||
def send(self, msg): | ||
self.stdin.write(msg) | ||
self.stdin.write("\n") | ||
self.stdin.flush() | ||
|
||
def receive(self): | ||
if self._stdout_buffer: | ||
return self._stdout_buffer.pop(0) | ||
|
||
while True: | ||
if not self.host_process.is_running: | ||
return None | ||
|
||
self.stdout.seek(self.current_line) | ||
lines = self.stdout.readlines() | ||
if not (lines and lines[-1].endswith("\n")): | ||
continue | ||
|
||
self.current_line = self.stdout.tell() | ||
self._stdout_buffer = [line.rstrip('\n') for line in lines] | ||
return self.receive() | ||
|
||
@ipc_call("GetRegister", [str]) | ||
def get_register(register, frame): | ||
top_frame = self.core_process.selected_thread.frame[0] | ||
reg_value = top_frame.FindRegister(register).unsigned | ||
return "0x%x" % reg_value | ||
|
||
@ipc_call("GetStaticData", [int, str]) | ||
def get_static_data(self, byte_count, name): | ||
value = "00" * byte_count | ||
for m in self.core_target.module_iter(): | ||
symbol = m.FindSymbol(name) | ||
if symbol: | ||
if symbol.end_addr.offset - symbol.addr.offset != byte_count: | ||
logger("We're in a hard pickle, yes we are") | ||
continue | ||
error = lldb.SBError() | ||
maybe_value = self.core_target.ReadMemory(symbol.addr, byte_count, error) | ||
if error.Fail(): | ||
logger("Oopsie doopsie!") | ||
continue | ||
value = "".join([("%x" % ord(c)).zfill(2) for c in maybe_value]) | ||
break | ||
return value | ||
|
||
@ipc_call("GetTlsData", [int]) | ||
def get_tls_data(self, key): | ||
return "%x" % TLSAccessor(self.debugger).getspecific(key) | ||
|
||
def listen(self): | ||
reading_buffer = False | ||
buf = [] | ||
while True: | ||
request = self.receive() | ||
|
||
if not self.host_process.is_running: | ||
return False | ||
|
||
if not request: | ||
continue | ||
|
||
if request == "end": | ||
break | ||
|
||
if request == "-- start buffer --": | ||
reading_buffer = True | ||
continue | ||
|
||
if request == "-- end buffer --": | ||
return "\n".join(buf) | ||
|
||
if reading_buffer: | ||
buf.append(request) | ||
continue | ||
|
||
if request.startswith("return"): | ||
return request.split(" ", 1)[1] | ||
|
||
ipc_method = self.ipc_methods.get(request.split(" ")[0], None) | ||
if ipc_method: | ||
self.send(ipc_method(*request.split(" ")[1:])) | ||
|
||
return True | ||
|
||
# TODO cleanup everything (files, target, process, etc.) | ||
def __delete__(self): | ||
pass |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,59 @@ | ||
import re | ||
import lldb | ||
|
||
TLS_DATA_RE = re.compile("data = (0x[0-9a-fA-F]+)") | ||
|
||
class TLSAccessor(object): | ||
def __init__(self, debugger): | ||
self.debugger = debugger | ||
|
||
|
||
def getspecific(self, key): | ||
""" | ||
__pthread_getspecific (glibc 2.27): | ||
void * | ||
__pthread_getspecific (pthread_key_t key) | ||
{ | ||
struct __pthread *self; | ||
if (key < 0 || key >= __pthread_key_count | ||
|| __pthread_key_destructors[key] == PTHREAD_KEY_INVALID) | ||
return NULL; | ||
self = _pthread_self (); | ||
if (key >= self->thread_specifics_size) | ||
return 0; | ||
return self->thread_specifics[key]; | ||
} | ||
""" | ||
|
||
interpreter = self.debugger.GetCommandInterpreter() | ||
# https://stackoverflow.com/a/10859835/2956796 | ||
command_line = "p ((struct pthread*)0x%x)->specific[%d/32][%d%%32]" % (self._pthread_self, key, key) | ||
result = lldb.SBCommandReturnObject() | ||
result_status = interpreter.HandleCommand(command_line, result) | ||
|
||
if not result.Succeeded(): | ||
print "Couldn't read TLS data for key %d" % key | ||
return 0 | ||
search_result = TLS_DATA_RE.search(result.GetOutput()) | ||
if not search_result: | ||
print "Couldn't parse TLS data for key %d" % key | ||
return 0 | ||
|
||
return int(search_result.group(1), 16) | ||
|
||
@property | ||
def _pthread_self(self): | ||
""" | ||
__thread struct __pthread *___pthread_self; | ||
""" | ||
# 0x7ffff6e7a03f <+15>: movq %fs:0x10, %rdx | ||
|
||
target = self.debugger.GetSelectedTarget() | ||
process = target.process | ||
top_frame = process.selected_thread.frame[0] | ||
|
||
return top_frame.FindRegister("fs_base").unsigned |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,242 @@ | ||
#include <string> | ||
#include <sstream> | ||
#include <iostream> | ||
#include <fstream> // std::fstream | ||
#include <stdlib.h> /* rand */ | ||
#include <unistd.h> | ||
#include <cstring> | ||
#include <cstdio> | ||
|
||
#include <fcntl.h> | ||
#include <sys/mman.h> | ||
|
||
#include <v8-postmortem-debugger.h> | ||
|
||
#define LOGGER(M) std::cerr << "[postmortem host] " << M << std::endl | ||
|
||
static std::ifstream input; | ||
static std::ofstream output; | ||
|
||
// TODO (mmarchini): V8 should provide a way to determine the register name | ||
#define GENERAL_REGISTERS(V) \ | ||
V(rax) \ | ||
V(rcx) \ | ||
V(rdx) \ | ||
V(rbx) \ | ||
V(rsp) \ | ||
V(rbp) \ | ||
V(rsi) \ | ||
V(rdi) \ | ||
V(r8) \ | ||
V(r9) \ | ||
V(r10) \ | ||
V(r11) \ | ||
V(r12) \ | ||
V(r13) \ | ||
V(r14) \ | ||
V(r15) | ||
|
||
enum RegisterCode { | ||
#define REGISTER_CODE(R) kRegCode_##R, | ||
GENERAL_REGISTERS(REGISTER_CODE) | ||
#undef REGISTER_CODE | ||
kRegAfterLast | ||
}; | ||
|
||
void LoadMemoryAddresses() { | ||
std::string buffer; | ||
|
||
while (true) { | ||
getline(input, buffer); | ||
if (input.eof()) { | ||
input.clear(); | ||
input.sync(); | ||
if (buffer.empty()) continue; | ||
} | ||
|
||
std::cerr << buffer << std::endl; | ||
if (buffer == "done") break; | ||
|
||
char fromBuffer[buffer.length() + 1] = { 0 }; | ||
buffer.copy(fromBuffer, buffer.length()); | ||
unsigned long long addr = std::stoul(strtok(fromBuffer, " "), nullptr, 16); | ||
size_t len = std::stoul(strtok(nullptr, " ")); | ||
char* filePath = strtok(nullptr, " "); | ||
|
||
// TODO (mmarchini) change mmap with something cross-platform | ||
std::cerr << filePath << std::endl; | ||
int fd = open(filePath, O_RDWR); | ||
|
||
if (fd == -1) { | ||
std::cerr << "error while opening file '" << filePath << "': " << strerror(errno) << std::endl; | ||
output << "-1" << std::endl; | ||
continue; | ||
} | ||
|
||
void* result = mmap(reinterpret_cast<void*>(addr), static_cast<size_t>(len), | ||
PROT_READ | PROT_WRITE | PROT_EXEC, MAP_FIXED | MAP_SHARED, fd, 0); | ||
|
||
if (result == MAP_FAILED) { | ||
std::cerr << "map failed " << strerror(errno) << std::endl; | ||
output << "-1" << std::endl; | ||
} else if (result != reinterpret_cast<void*>(addr)) { | ||
std::cerr << "allocated in incorrect address 0x" << std::hex << result << std::dec << std::endl; | ||
output << "-1" << std::endl; | ||
} else { | ||
output << len << std::endl; | ||
} | ||
|
||
// TODO(mmarchini): Check if we can close the fd when a file is mapped to memory | ||
close(fd); | ||
} | ||
} | ||
|
||
|
||
uintptr_t GetRegister(int index) { | ||
switch (index) { | ||
#define REGISTER_CODE(R) \ | ||
case kRegCode_##R: \ | ||
output << "GetRegister " << #R << std::endl; \ | ||
break; | ||
GENERAL_REGISTERS(REGISTER_CODE) | ||
#undef REGISTER_CODE | ||
default: | ||
std::cerr << "Couldn't determine register for index " << index << std::endl; | ||
return 0; | ||
} | ||
|
||
std::string buffer; | ||
while (true) { | ||
getline(input, buffer); | ||
if (input.eof()) { | ||
input.clear(); | ||
input.sync(); | ||
if (buffer.empty()) continue; | ||
} | ||
return std::stoul(buffer, nullptr, 16); | ||
} | ||
} | ||
|
||
|
||
void* GetTlsData(int32_t key) { | ||
output << "GetTlsData " << key << std::endl; | ||
// LOGGER("Loading tls data"); | ||
|
||
std::string buffer; | ||
while (true) { | ||
getline(input, buffer); | ||
if (input.eof()) { | ||
input.clear(); | ||
input.sync(); | ||
if (buffer.empty()) continue; | ||
} | ||
|
||
// LOGGER("here ya go"); | ||
// LOGGER(buffer); | ||
|
||
unsigned long long value = std::stoul(buffer, nullptr, 16); | ||
|
||
// LOGGER("result: " << std::hex << value << std::dec); | ||
return reinterpret_cast<void*>(value); | ||
} | ||
} | ||
|
||
StaticAccessResult GetStaticData(const char* name, uint8_t* destination, | ||
size_t byte_count) { | ||
output << "GetStaticData " << byte_count << " " << name << std::endl; | ||
|
||
std::string buffer; | ||
while (true) { | ||
getline(input, buffer); | ||
if (input.eof()) { | ||
input.clear(); | ||
input.sync(); | ||
if (buffer.empty()) continue; | ||
} | ||
|
||
if (buffer.length() != byte_count * 2) { | ||
return StaticAccessResult::kGenericError; | ||
} | ||
|
||
const char* c_buffer = buffer.c_str(); | ||
|
||
for (unsigned int i = 0; i < byte_count; i++) { | ||
char a[3] = { c_buffer[(i * 2)], c_buffer[(i * 2) + 1], '\0' }; | ||
destination[i] = static_cast<uint8_t>(std::stoul(a, nullptr, 16)); | ||
} | ||
|
||
return StaticAccessResult::kOk; | ||
} | ||
return StaticAccessResult::kOk; | ||
} | ||
|
||
|
||
int PostmortemHost(int argc, char* argv[]) { | ||
input = std::ifstream(argv[2]); | ||
output = std::ofstream(argv[3]); | ||
|
||
std::cerr << argv[2] << std::endl; | ||
std::cerr << argv[3] << std::endl; | ||
|
||
std::string buffer; | ||
|
||
LoadMemoryAddresses(); | ||
|
||
while (true) { | ||
getline(input, buffer); | ||
if (input.eof()) { | ||
input.clear(); | ||
input.sync(); | ||
if (buffer.empty()) continue; | ||
} | ||
|
||
switch (buffer.c_str()[0]) { | ||
case 's': { | ||
std::FILE* tmp = std::tmpfile(); | ||
char fromBuffer[buffer.length() + 1] = { 0 }; | ||
buffer.copy(fromBuffer, buffer.length()); | ||
strtok(fromBuffer, " "); | ||
uintptr_t stack_pointer = std::stoul(strtok(nullptr, " ")); | ||
uintptr_t program_counter = std::stoul(strtok(nullptr, " ")); | ||
V8PostmortemPrintStackTrace(stack_pointer, program_counter, | ||
&GetRegister, &GetTlsData, &GetStaticData, | ||
tmp); | ||
output << "-- start buffer --" << std::endl; | ||
{ | ||
size_t len = 0; | ||
ssize_t read; | ||
char* line = nullptr; | ||
std::fseek(tmp, 0, SEEK_SET); | ||
|
||
while ((read = getline(&line, &len, tmp)) != -1) { | ||
output << line << std::endl; | ||
} | ||
} | ||
output << "-- end buffer --" << std::endl; | ||
break; | ||
} | ||
case 'p': { | ||
std::ostringstream tmp; | ||
char fromBuffer[buffer.length() + 1] = { 0 }; | ||
buffer.copy(fromBuffer, buffer.length()); | ||
strtok(fromBuffer, " "); | ||
uintptr_t obj = std::stoul(strtok(nullptr, " "), nullptr, 16); | ||
|
||
V8PostmortemPrintObject(reinterpret_cast<void*>(obj), &GetRegister, | ||
&GetTlsData, &GetStaticData, tmp); | ||
|
||
output << "-- start buffer --" << std::endl; | ||
{ | ||
output << tmp.str() << std::endl; | ||
} | ||
output << "-- end buffer --" << std::endl; | ||
break; | ||
} | ||
default: | ||
std::cerr << "Invalid option '" << buffer.c_str()[0] << "'" << std::endl; | ||
break; | ||
} | ||
} | ||
|
||
return 0; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
|
||
int PostmortemHost(int argc, char* argv[]); |