diff --git a/include/eosio/vm/allocator.hpp b/include/eosio/vm/allocator.hpp index e674b28c..04aab812 100644 --- a/include/eosio/vm/allocator.hpp +++ b/include/eosio/vm/allocator.hpp @@ -316,6 +316,8 @@ namespace eosio { namespace vm { mprotect(_code_base, _code_size, PROT_NONE); } + const void* get_code_start() const { return _code_base; } + /* different semantics than free, * the memory must be at the end of the most recently allocated block. */ diff --git a/include/eosio/vm/backend.hpp b/include/eosio/vm/backend.hpp index 54ba6a64..f74cba41 100644 --- a/include/eosio/vm/backend.hpp +++ b/include/eosio/vm/backend.hpp @@ -24,32 +24,40 @@ namespace eosio { namespace vm { struct jit { template using context = jit_execution_context; - template - using parser = binary_parser>, Options>; + template + using parser = binary_parser>, Options, DebugInfo>; + static constexpr bool is_jit = true; + }; + + struct jit_profile { + template + using context = jit_execution_context; + template + using parser = binary_parser>, Options, DebugInfo>; static constexpr bool is_jit = true; }; struct interpreter { template using context = execution_context; - template - using parser = binary_parser; + template + using parser = binary_parser; static constexpr bool is_jit = false; }; struct null_backend { template using context = null_execution_context; - template - using parser = binary_parser; + template + using parser = binary_parser; static constexpr bool is_jit = false; }; - template + template class backend { using host_t = detail::host_type_t; using context_t = typename Impl::template context; - using parser_t = typename Impl::template parser; + using parser_t = typename Impl::template parser; void construct(host_t* host=nullptr) { mod.finalize(); ctx.set_wasm_allocator(memory_alloc); @@ -61,32 +69,32 @@ namespace eosio { namespace vm { } public: backend(wasm_code&& code, host_t& host, wasm_allocator* alloc, const Options& options = Options{}) - : memory_alloc(alloc), ctx(parser_t{ mod.allocator, options }.parse_module(code, mod), detail::get_max_call_depth(options)) { + : memory_alloc(alloc), ctx(parser_t{ mod.allocator, options }.parse_module(code, mod, debug), detail::get_max_call_depth(options)) { ctx.set_max_pages(detail::get_max_pages(options)); construct(&host); } backend(wasm_code&& code, wasm_allocator* alloc, const Options& options = Options{}) - : memory_alloc(alloc), ctx(parser_t{ mod.allocator, options }.parse_module(code, mod), detail::get_max_call_depth(options)) { + : memory_alloc(alloc), ctx(parser_t{ mod.allocator, options }.parse_module(code, mod, debug), detail::get_max_call_depth(options)) { ctx.set_max_pages(detail::get_max_pages(options)); construct(); } backend(wasm_code& code, host_t& host, wasm_allocator* alloc, const Options& options = Options{}) - : memory_alloc(alloc), ctx(parser_t{ mod.allocator, options }.parse_module(code, mod), detail::get_max_call_depth(options)) { + : memory_alloc(alloc), ctx(parser_t{ mod.allocator, options }.parse_module(code, mod, debug), detail::get_max_call_depth(options)) { ctx.set_max_pages(detail::get_max_pages(options)); construct(&host); } backend(wasm_code& code, wasm_allocator* alloc, const Options& options = Options{}) - : memory_alloc(alloc), ctx(parser_t{ mod.allocator, options }.parse_module(code, mod), detail::get_max_call_depth(options)) { + : memory_alloc(alloc), ctx(parser_t{ mod.allocator, options }.parse_module(code, mod, debug), detail::get_max_call_depth(options)) { ctx.set_max_pages(detail::get_max_pages(options)); construct(); } backend(wasm_code_ptr& ptr, size_t sz, host_t& host, wasm_allocator* alloc, const Options& options = Options{}) - : memory_alloc(alloc), ctx(parser_t{ mod.allocator, options }.parse_module2(ptr, sz, mod), detail::get_max_call_depth(options)) { + : memory_alloc(alloc), ctx(parser_t{ mod.allocator, options }.parse_module2(ptr, sz, mod, debug), detail::get_max_call_depth(options)) { ctx.set_max_pages(detail::get_max_pages(options)); construct(&host); } backend(wasm_code_ptr& ptr, size_t sz, wasm_allocator* alloc, const Options& options = Options{}) - : memory_alloc(alloc), ctx(parser_t{ mod.allocator, options }.parse_module2(ptr, sz, mod), detail::get_max_call_depth(options)) { + : memory_alloc(alloc), ctx(parser_t{ mod.allocator, options }.parse_module2(ptr, sz, mod, debug), detail::get_max_call_depth(options)) { ctx.set_max_pages(detail::get_max_pages(options)); construct(); } @@ -236,9 +244,12 @@ namespace eosio { namespace vm { inline void exit(const std::error_code& ec) { ctx.exit(ec); } inline auto& get_context() { return ctx; } + const DebugInfo& get_debug() const { return debug; } + private: wasm_allocator* memory_alloc = nullptr; // non owning pointer module mod; + DebugInfo debug; context_t ctx; }; }} // namespace eosio::vm diff --git a/include/eosio/vm/bitcode_writer.hpp b/include/eosio/vm/bitcode_writer.hpp index 4d734bc3..ee469e01 100644 --- a/include/eosio/vm/bitcode_writer.hpp +++ b/include/eosio/vm/bitcode_writer.hpp @@ -295,11 +295,16 @@ namespace eosio { namespace vm { } void finalize(function_body& body) { - fb.resize(op_index + 1); + op_index++; + fb.resize(op_index); body.code = fb.raw(); - body.size = op_index + 1; + body.size = op_index; _base_offset += body.size; } + + const void* get_addr() const { return fb.raw() + op_index; } + const void* get_base_addr() const { return _code_segment_base; } + private: growable_allocator& _allocator; diff --git a/include/eosio/vm/debug_info.hpp b/include/eosio/vm/debug_info.hpp new file mode 100644 index 00000000..a666c8ec --- /dev/null +++ b/include/eosio/vm/debug_info.hpp @@ -0,0 +1,102 @@ +#pragma once + +#include +#include +#include +#include +#include + +namespace eosio::vm { + +struct null_debug_info { + using builder = null_debug_info; + void on_code_start(const void* compiled_base, const void* wasm_code_start) {} + void on_function_start(const void* code_addr, const void* wasm_addr) {} + void on_instr_start(const void* code_addr, const void* wasm_addr) {} + void on_code_end(const void* code_addr, const void* wasm_addr) {} + void set(const null_debug_info&) {} + void relocate(const void*) {} +}; + +// Maps a contiguous region of code to offsets onto the code section of the original wasm. +class profile_instr_map { + struct addr_entry { + uint32_t offset; + uint32_t wasm_addr; + }; + +public: + + struct builder { + void on_code_start(const void* compiled_base, const void* wasm_code_start) { + code_base = compiled_base; + wasm_base = wasm_code_start; + } + void on_function_start(const void* code_addr, const void* wasm_addr) { + data.push_back({ + static_cast(reinterpret_cast(code_addr) - reinterpret_cast(code_base)), + static_cast(reinterpret_cast(wasm_addr) - reinterpret_cast(wasm_base)) + }); + } + void on_instr_start(const void* code_addr, const void* wasm_addr) { + data.push_back({ + static_cast(reinterpret_cast(code_addr) - reinterpret_cast(code_base)), + static_cast(reinterpret_cast(wasm_addr) - reinterpret_cast(wasm_base)) + }); + } + void on_code_end(const void* code_addr, const void* wasm_addr) { + code_end = code_addr; + } + + const void* code_base = nullptr; + const void* wasm_base = nullptr; + const void* code_end = nullptr; + std::vector data; + }; + + void set(builder&& b) { + data = std::move(b.data); + std::sort(data.begin(), data.end(), [](const addr_entry& lhs, const addr_entry& rhs){ return lhs.offset < rhs.offset; }); + base_address = b.code_base; + code_size = reinterpret_cast(b.code_end) - reinterpret_cast(base_address); + offset_to_addr = data.data(); + offset_to_addr_len = data.size(); + } + + profile_instr_map() = default; + profile_instr_map(const profile_instr_map&) = delete; + profile_instr_map& operator=(const profile_instr_map&) = delete; + + // Indicate that the executable code was moved/copied/mmapped/etc to another location + void relocate(const void* new_base) { base_address = new_base; } + + // Cannot use most of the standard library as the STL is not async-signal-safe + std::uint32_t translate(const void* pc) const { + std::size_t diff = (reinterpret_cast(pc) - reinterpret_cast(base_address)); // negative values wrap + if(diff >= code_size || diff < offset_to_addr[0].offset) return 0xFFFFFFFFu; + std::uint32_t offset = diff; + + // Loop invariant: offset_to_addr[lower].offset <= offset < offset_to_addr[upper].offset + std::size_t lower = 0, upper = offset_to_addr_len; + while(upper - lower > 1) { + std::size_t mid = lower + (upper - lower) / 2; + if(offset_to_addr[mid].offset <= offset) { + lower = mid; + } else { + upper = mid; + } + } + + return offset_to_addr[lower].wasm_addr; + } +private: + const void* base_address = nullptr; + std::size_t code_size = 0; + + addr_entry* offset_to_addr = nullptr; + std::size_t offset_to_addr_len = 0; + + std::vector data; +}; + +} diff --git a/include/eosio/vm/exceptions.hpp b/include/eosio/vm/exceptions.hpp index 199db233..2cf8b14f 100644 --- a/include/eosio/vm/exceptions.hpp +++ b/include/eosio/vm/exceptions.hpp @@ -42,6 +42,7 @@ namespace eosio { namespace vm { DECLARE_EXCEPTION( timeout_exception, 4010001, "timeout" ) DECLARE_EXCEPTION( wasm_exit_exception, 4010002, "exit" ) DECLARE_EXCEPTION( span_exception, 4020000, "span exception" ) + DECLARE_EXCEPTION( profile_exception, 4030000, "profile exception" ) }} // eosio::vm #undef DECLARE_EXCEPTION diff --git a/include/eosio/vm/execution_context.hpp b/include/eosio/vm/execution_context.hpp index 2a7ad9f0..4207b5fe 100644 --- a/include/eosio/vm/execution_context.hpp +++ b/include/eosio/vm/execution_context.hpp @@ -24,6 +24,14 @@ #include #include +// OSX requires _XOPEN_SOURCE to #include +#ifdef __APPLE__ +#ifndef _XOPEN_SOURCE +#define _XOPEN_SOURCE 700 +#endif +#endif +#include + namespace eosio { namespace vm { struct null_host_functions { @@ -192,9 +200,17 @@ namespace eosio { namespace vm { null_execution_context(module& m, std::uint32_t max_call_depth) : base_type(m) {} }; - template - class jit_execution_context : public execution_context_base, Host> { - using base_type = execution_context_base, Host>; + template + struct frame_info_holder {}; + template<> + struct frame_info_holder { + void* volatile _bottom_frame = nullptr; + void* volatile _top_frame = nullptr; + }; + + template + class jit_execution_context : public frame_info_holder, public execution_context_base, Host> { + using base_type = execution_context_base, Host>; using host_type = detail::host_type_t; public: using base_type::execute; @@ -283,9 +299,25 @@ namespace eosio { namespace vm { } auto fn = reinterpret_cast(_mod.code[func_index - _mod.get_imported_functions_size()].jit_code_offset + _mod.allocator._code_base); - vm::invoke_with_signal_handler([&]() { - result = execute(args_raw, fn, this, base_type::linear_memory(), stack); - }, &handle_signal); + if constexpr(EnableBacktrace) { + sigset_t block_mask; + sigemptyset(&block_mask); + sigaddset(&block_mask, SIGPROF); + pthread_sigmask(SIG_BLOCK, &block_mask, nullptr); + auto restore = scope_guard{[this, &block_mask] { + this->_top_frame = nullptr; + this->_bottom_frame = nullptr; + pthread_sigmask(SIG_UNBLOCK, &block_mask, nullptr); + }}; + + vm::invoke_with_signal_handler([&]() { + result = execute(args_raw, fn, this, base_type::linear_memory(), stack); + }, handle_signal); + } else { + vm::invoke_with_signal_handler([&]() { + result = execute(args_raw, fn, this, base_type::linear_memory(), stack); + }, handle_signal); + } } } catch(wasm_exit_exception&) { return {}; @@ -302,6 +334,62 @@ namespace eosio { namespace vm { } __builtin_unreachable(); } + + int backtrace(void** out, int count, void* uc) const { + static_assert(EnableBacktrace); + void* end = this->_top_frame; + if(end == nullptr) return 0; + void* rbp; + int i = 0; + if(this->_bottom_frame) { + rbp = this->_bottom_frame; + } else if(count != 0) { + if(uc) { +#ifdef __APPLE__ + auto rip = reinterpret_cast(static_cast(uc)->uc_mcontext->__ss.__rip); + rbp = reinterpret_cast(static_cast(uc)->uc_mcontext->__ss.__rbp); + auto rsp = reinterpret_cast(static_cast(uc)->uc_mcontext->__ss.__rsp); +#else + auto rip = reinterpret_cast(static_cast(uc)->uc_mcontext.gregs[REG_RIP]); + rbp = reinterpret_cast(static_cast(uc)->uc_mcontext.gregs[REG_RBP]); + auto rsp = reinterpret_cast(static_cast(uc)->uc_mcontext.gregs[REG_RSP]); +#endif + out[i++] = rip; + // If we were interrupted in the function prologue or epilogue, + // avoid dropping the parent frame. + auto code_base = reinterpret_cast(_mod.allocator.get_code_start()); + auto code_end = code_base + _mod.allocator._code_size; + if(rip >= code_base && rip < code_end && count > 1) { + // function prologue + if(*reinterpret_cast(rip) == 0x55) { + if(rip != *static_cast(rsp)) { // Ignore fake frame set up for softfloat calls + out[i++] = *static_cast(rsp); + } + } else if(rip[0] == 0x48 && rip[1] == 0x89 && (rip[2] == 0xe5 || rip[2] == 0x27)) { + if((rip - 1) != static_cast(rsp)[1]) { // Ignore fake frame set up for softfloat calls + out[i++] = static_cast(rsp)[1]; + } + } + // function epilogue + else if(rip[0] == 0xc3) { + out[i++] = *static_cast(rsp); + } + } + } else { + rbp = __builtin_frame_address(0); + } + } + while(i < count) { + void* rip = static_cast(rbp)[1]; + if(rbp == end) break; + out[i++] = rip; + rbp = *static_cast(rbp); + } + return i; + } + + static constexpr bool async_backtrace() { return EnableBacktrace; } + protected: template @@ -325,48 +413,59 @@ namespace eosio { namespace vm { // currently ignoring register c++17 warning register void* stack_top asm ("r12") = stack; // 0x1f80 is the default MXCSR value - asm volatile( - "test %[stack_top], %[stack_top]; " - "jnz 3f; " - "mov %%rsp, %[stack_top]; " - "sub $0x98, %%rsp; " // red-zone + 24 bytes - "mov %[stack_top], (%%rsp); " - "jmp 4f; " - "3: " - "mov %%rsp, (%[stack_top]); " - "mov %[stack_top], %%rsp; " - "4: " - "stmxcsr 16(%%rsp); " - "mov $0x1f80, %%rax; " - "mov %%rax, 8(%%rsp); " - "ldmxcsr 8(%%rsp); " - "mov %[Count], %%rax; " - "test %%rax, %%rax; " - "jz 2f; " - "1: " - "movq (%[data]), %%r8; " - "lea 8(%[data]), %[data]; " - "pushq %%r8; " - "dec %%rax; " - "jnz 1b; " - "2: " - "callq *%[fun]; " - "add %[StackOffset], %%rsp; " - "ldmxcsr 16(%%rsp); " - "mov (%%rsp), %%rsp; " - // Force explicit register allocation, because otherwise it's too hard to get the clobbers right. - : [result] "=&a" (result), // output, reused as a scratch register - [data] "+d" (data), [fun] "+c" (fun), [stack_top] "+r" (stack_top) // input only, but may be clobbered - : [context] "D" (context), [linear_memory] "S" (linear_memory), - [StackOffset] "n" (Count*8), [Count] "n" (Count), "b" (stack_check) // input - : "memory", "cc", // clobber - // call clobbered registers, that are not otherwise used - /*"rax", "rcx", "rdx", "rsi", "rdi",*/ "r8", "r9", "r10", "r11", - "xmm0", "xmm1", "xmm2", "xmm3", "xmm4", "xmm5", "xmm6", "xmm7", - "xmm8", "xmm9", "xmm10", "xmm11", "xmm12", "xmm13", "xmm14", "xmm15", - "mm0","mm1", "mm2", "mm3", "mm4", "mm5", "mm6", "mm6", - "st", "st(1)", "st(2)", "st(3)", "st(4)", "st(5)", "st(6)", "st(7)" +#define ASM_CODE(before, after) \ + asm volatile( \ + "test %[stack_top], %[stack_top]; " \ + "jnz 3f; " \ + "mov %%rsp, %[stack_top]; " \ + "sub $0x98, %%rsp; " /* red-zone + 24 bytes*/ \ + "mov %[stack_top], (%%rsp); " \ + "jmp 4f; " \ + "3: " \ + "mov %%rsp, (%[stack_top]); " \ + "mov %[stack_top], %%rsp; " \ + "4: " \ + "stmxcsr 16(%%rsp); " \ + "mov $0x1f80, %%rax; " \ + "mov %%rax, 8(%%rsp); " \ + "ldmxcsr 8(%%rsp); " \ + "mov %[Count], %%rax; " \ + "test %%rax, %%rax; " \ + "jz 2f; " \ + "1: " \ + "movq (%[data]), %%r8; " \ + "lea 8(%[data]), %[data]; " \ + "pushq %%r8; " \ + "dec %%rax; " \ + "jnz 1b; " \ + "2: " \ + before \ + "callq *%[fun]; " \ + after \ + "add %[StackOffset], %%rsp; " \ + "ldmxcsr 16(%%rsp); " \ + "mov (%%rsp), %%rsp; " \ + /* Force explicit register allocation, because otherwise it's too hard to get the clobbers right. */ \ + : [result] "=&a" (result), /* output, reused as a scratch register */ \ + [data] "+d" (data), [fun] "+c" (fun), [stack_top] "+r" (stack_top) /* input only, but may be clobbered */ \ + : [context] "D" (context), [linear_memory] "S" (linear_memory), \ + [StackOffset] "n" (Count*8), [Count] "n" (Count), "b" (stack_check) /* input */ \ + : "memory", "cc", /* clobber */ \ + /* call clobbered registers, that are not otherwise used */ \ + /*"rax", "rcx", "rdx", "rsi", "rdi",*/ "r8", "r9", "r10", "r11", \ + "xmm0", "xmm1", "xmm2", "xmm3", "xmm4", "xmm5", "xmm6", "xmm7", \ + "xmm8", "xmm9", "xmm10", "xmm11", "xmm12", "xmm13", "xmm14", "xmm15", \ + "mm0","mm1", "mm2", "mm3", "mm4", "mm5", "mm6", "mm6", \ + "st", "st(1)", "st(2)", "st(3)", "st(4)", "st(5)", "st(6)", "st(7)" \ ); + if constexpr (!EnableBacktrace) { + ASM_CODE("", ""); + } else { + ASM_CODE("movq %%rbp, 8(%[context]); ", + "xor %[fun], %[fun]; " + "mov %[fun], 8(%[context]); "); + } +#undef ASM_CODE return result; } @@ -630,6 +729,19 @@ namespace eosio { namespace vm { } } + // This isn't async-signal-safe. Cross fingers and hope for the best. + // It's only used for profiling. + int backtrace(void** data, int limit, void* uc) const { + int out = 0; + if(limit != 0) { + data[out++] = _state.pc; + } + for(int i = 0; out < limit && i < _as.size(); ++i) { + data[out++] = _as.get_back(i).pc; + } + return out; + } + private: template diff --git a/include/eosio/vm/null_writer.hpp b/include/eosio/vm/null_writer.hpp index f1e77442..e8f95d1f 100644 --- a/include/eosio/vm/null_writer.hpp +++ b/include/eosio/vm/null_writer.hpp @@ -201,6 +201,9 @@ class null_writer { void emit_prologue(const func_type& /*ft*/, const guarded_vector& /*locals*/, uint32_t /*idx*/) {} void emit_epilogue(const func_type& /*ft*/, const guarded_vector& /*locals*/, uint32_t /*idx*/) {} void finalize(function_body& /*body*/) {} + + const void* get_addr() const { return nullptr; } + const void* get_base_addr() const { return nullptr; } }; }} diff --git a/include/eosio/vm/parser.hpp b/include/eosio/vm/parser.hpp index b91fea55..b6bc7035 100644 --- a/include/eosio/vm/parser.hpp +++ b/include/eosio/vm/parser.hpp @@ -9,6 +9,7 @@ #include #include #include +#include #include #include @@ -190,12 +191,14 @@ namespace eosio { namespace vm { PARSER_OPTION(allow_zero_blocktype, false, bool) + PARSER_OPTION(parse_custom_section_name, false, bool); + #undef MAX_ELEMENTS #undef PARSER_OPTION } - template + template class binary_parser { public: explicit binary_parser(growable_allocator& alloc, const Options& options = Options{}) : _allocator(alloc), _options(options) {} @@ -286,18 +289,18 @@ namespace eosio { namespace vm { return result; } - inline module& parse_module(wasm_code& code, module& mod) { + inline module& parse_module(wasm_code& code, module& mod, DebugInfo& debug) { wasm_code_ptr cp(code.data(), code.size()); - parse_module(cp, code.size(), mod); + parse_module(cp, code.size(), mod, debug); return mod; } - inline module& parse_module2(wasm_code_ptr& code_ptr, size_t sz, module& mod) { - parse_module(code_ptr, sz, mod); + inline module& parse_module2(wasm_code_ptr& code_ptr, size_t sz, module& mod, DebugInfo& debug) { + parse_module(code_ptr, sz, mod, debug); return mod; } - void parse_module(wasm_code_ptr& code_ptr, size_t sz, module& mod) { + void parse_module(wasm_code_ptr& code_ptr, size_t sz, module& mod, DebugInfo& debug) { _mod = &mod; EOS_VM_ASSERT(parse_magic(code_ptr) == constants::magic, wasm_parse_exception, "magic number did not match"); EOS_VM_ASSERT(parse_version(code_ptr) == constants::version, wasm_parse_exception, @@ -341,6 +344,9 @@ namespace eosio { namespace vm { } } EOS_VM_ASSERT(_mod->code.size() == _mod->functions.size(), wasm_parse_exception, "code section must have the same size as the function section" ); + + debug.set(std::move(imap)); + debug.relocate(_allocator.get_code_start()); } inline uint32_t parse_magic(wasm_code_ptr& code) { @@ -355,9 +361,59 @@ namespace eosio { namespace vm { } inline void parse_custom(wasm_code_ptr& code) { - parse_utf8_string(code, 0xFFFFFFFFu); // ignored, but needs to be validated - // skip to the end of the section - code += code.bounds() - code.offset(); + auto section_name = parse_utf8_string(code, 0xFFFFFFFFu); // ignored, but needs to be validated + if(detail::get_parse_custom_section_name(_options) && + section_name.size() == 4 && std::memcmp(section_name.raw(), "name", 4) == 0) { + parse_name_section(code); + } else { + // skip to the end of the section + code += code.bounds() - code.offset(); + } + } + + inline void parse_name_map(wasm_code_ptr& code, guarded_vector& map) { + for(uint32_t i = 0; i < map.size(); ++i) { + map[i].idx = parse_varuint32(code); + map[i].name = parse_utf8_string(code, 0xFFFFFFFFu); + } + } + + inline void parse_name_section(wasm_code_ptr& code) { + _mod->names = _allocator.alloc(1); + new (_mod->names) name_section; + if(code.bounds() == code.offset()) return; + if(*code == 0) { + ++code; + auto subsection_guard = code.scoped_consume_items(parse_varuint32(code)); + _mod->names->module_name = _allocator.alloc>(1); + new (_mod->names->module_name) guarded_vector(parse_utf8_string(code, 0xFFFFFFFFu)); + } + if(code.bounds() == code.offset()) return; + if(*code == 1) { + ++code; + auto subsection_guard = code.scoped_consume_items(parse_varuint32(code)); + uint32_t size = parse_varuint32(code); + _mod->names->function_names = _allocator.alloc>(1); + new (_mod->names->function_names) guarded_vector(_allocator, size); + parse_name_map(code, *_mod->names->function_names); + } + if(code.bounds() == code.offset()) return; + if(*code == 2) { + ++code; + auto subsection_guard = code.scoped_consume_items(parse_varuint32(code)); + uint32_t size = parse_varuint32(code); + _mod->names->local_names = _allocator.alloc>(1); + new (_mod->names->local_names) guarded_vector(_allocator, size); + for(uint32_t i = 0; i < size; ++i) { + auto& [idx,namemap] = (*_mod->names->local_names)[i]; + idx = parse_varuint32(code); + uint32_t local_size = parse_varuint32(code); + namemap = guarded_vector(_allocator, local_size); + parse_name_map(code, namemap); + } + } + if(code.bounds() == code.offset()) return; + EOS_VM_ASSERT(false, wasm_parse_exception, "Invalid subsection Id"); } void parse_import_entry(wasm_code_ptr& code, import_entry& entry) { @@ -677,6 +733,7 @@ namespace eosio { namespace vm { void parse_function_body_code(wasm_code_ptr& code, size_t bounds, const detail::max_func_local_bytes_stack_checker& local_bytes_checker, Writer& code_writer, const func_type& ft, const local_types_t& local_types) { + // Initialize the control stack with the current function as the sole element operand_stack_type_tracker op_stack{local_bytes_checker, _options}; std::vector pc_stack{{ @@ -742,6 +799,8 @@ namespace eosio { namespace vm { EOS_VM_ASSERT(pc_stack.size() <= detail::get_max_nested_structures(_options), wasm_parse_exception, "nested structures validation failure"); + imap.on_instr_start(code_writer.get_addr(), code.raw()); + switch (*code++) { case opcodes::unreachable: check_in_bounds(); code_writer.emit_unreachable(); op_stack.start_unreachable(); break; case opcodes::nop: code_writer.emit_nop(); break; @@ -1251,19 +1310,23 @@ namespace eosio { namespace vm { template inline void parse_section(wasm_code_ptr& code, vec>& elems) { + const void* code_start = code.raw() - code.offset(); parse_section_impl(code, elems, detail::get_max_function_section_elements(_options), [&](wasm_code_ptr& code, function_body& fb, std::size_t idx) { parse_function_body(code, fb, idx); }); EOS_VM_ASSERT( elems.size() == _mod->functions.size(), wasm_parse_exception, "code section must have the same size as the function section" ); Writer code_writer(_allocator, code.bounds() - code.offset(), *_mod); + imap.on_code_start(code_writer.get_base_addr(), code_start); for (size_t i = 0; i < _function_bodies.size(); i++) { function_body& fb = _mod->code[i]; func_type& ft = _mod->types.at(_mod->functions.at(i)); local_types_t local_types(ft, fb.locals); + imap.on_function_start(code_writer.get_addr(), _function_bodies[i].first.raw()); code_writer.emit_prologue(ft, fb.locals, i); parse_function_body_code(_function_bodies[i].first, fb.size, _function_bodies[i].second, code_writer, ft, local_types); code_writer.emit_epilogue(ft, fb.locals, i); code_writer.finalize(fb); } + imap.on_code_end(code_writer.get_addr(), code.raw()); } template inline void parse_section(wasm_code_ptr& code, @@ -1314,5 +1377,6 @@ namespace eosio { namespace vm { std::vector>> _function_bodies; detail::max_mutable_globals_checker _globals_checker; detail::eosio_max_nested_structures_checker _nested_checker; + typename DebugInfo::builder imap; }; }} // namespace eosio::vm diff --git a/include/eosio/vm/profile.hpp b/include/eosio/vm/profile.hpp new file mode 100644 index 00000000..c9160278 --- /dev/null +++ b/include/eosio/vm/profile.hpp @@ -0,0 +1,419 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace eosio::vm { + +inline uint32_t profile_interval_us = 10000; + +struct profile_data { + // buffer_size is the size of the I/O write buffer. The buffer must be at least large enough for one item. + // hash_table_size is the maximum number of unique traces that can be stored in memory. It must be a power of 2. + template + profile_data(const std::string& file, Backend& bkend, const std::size_t buffer_size = 65536, std::size_t hash_table_size = 1024) : + addr_map(bkend.get_debug()), + outbuf_storage(buffer_size), + items_storage(hash_table_size), + table_storage(hash_table_size) // load factor of 1 + { + init_backtrace(bkend.get_context()); + + outbuf = outbuf_storage.data(); + outpos = 0; + outsize = outbuf_storage.size(); + + list_header* prev = &mru_list; + for(auto& item : items_storage) { + item.mru.prev = prev; + item.mru.next = reinterpret_cast(&item + 1); // Avoid undefined behavior at the end + prev = &item.mru; + } + items_storage.back().mru.next = &mru_list; + mru_list.next = &items_storage.front().mru; + mru_list.prev = &items_storage.back().mru; + + table = table_storage.data(); + table_size = table_storage.size(); + + fd = open(file.c_str(), O_WRONLY | O_CREAT | O_TRUNC, 0755); + write_header(); + } + ~profile_data() { + flush_hash(); + write_trailer(); + flush(); + close(fd); + } + void write_header() { + uint32_t header[] = { + 0, + 3, + 0, + profile_interval_us, + 0 + }; + write(reinterpret_cast(header), sizeof(header)); + } + void write_trailer() { + uint32_t trailer[] = { 0, 1, 0 }; + write(reinterpret_cast(trailer), sizeof(trailer)); + } + void flush() { + for(std::size_t i = 0; i < outpos;) { + auto res = ::write(fd, outbuf + i, outpos - i); + if(res == -1) { + // report_error + break; + } else { + i += res; + } + } + outpos = 0; + } + + void write(const char* data, std::size_t size) { + if(size + outpos >= outsize) { + flush(); + } + std::memcpy(outbuf + outpos, data, size); + outpos += size; + } + + static constexpr std::size_t max_frames = 251; + + struct list_header { + list_header* next; + list_header* prev; + void unlink() { + next->prev = prev; + prev->next = next; + } + }; + + struct item { + list_header mru; + item* next; + uint32_t bucket = 0xFFFFFFFF; + uint32_t count = 0; + uint32_t len; + uint32_t frames[max_frames]; + // std::hash is not async-signal-safe + std::size_t hash() const { + // MurmurHash64A + // Including len gives a multiple of 2. + static_assert(max_frames % 2 == 1); + // Not strictly necessary for correctness, but avoids unaligned loads + static_assert(offsetof(item, len) % 8 == 0); + constexpr std::uint64_t mul = 0xc6a4a7935bd1e995ull; + constexpr std::uint64_t seed = 0xbadd00d00ull; + constexpr auto shift_mix = [](std::uint64_t v) { return v ^ (v >> 47); }; + int word_len = len/2+1; // if len is even, add an extra 0 word. + uint64_t hash = seed ^ (word_len * 8 * mul); + const char* base_addr = reinterpret_cast(&len); + for(int i = 0; i < word_len; ++i) { + std::uint64_t val; + memcpy(&val, base_addr + 8*i, 8); + hash = (hash ^ shift_mix(val * mul) * mul) * mul; + } + return shift_mix(shift_mix(hash) * mul); + } + }; + + static bool traces_equal(const item* lhs, const item* rhs) { + if(lhs->len != rhs->len) { + return false; + } + for(uint32_t i = 0; i < lhs->len; ++i) { + if(lhs->frames[i] != rhs->frames[i]) { + return false; + } + } + return true; + } + + void write(const item* item) { + write(reinterpret_cast(&item->count), (2+item->len) * sizeof(std::uint32_t)); + } + + item* evict_oldest() { + item* result = reinterpret_cast(mru_list.prev); + if(result->bucket != 0xFFFFFFFFu) { + write(result); + for(item** entry = &table[result->bucket]; ; entry = &(*entry)->next) { + if(*entry == result) { + *entry = result->next; + break; + } + } + result->bucket = 0xFFFFFFFFu; + } + return result; + } + + void move_to_head(list_header* new_head) { + new_head->unlink(); + new_head->next = mru_list.next; + new_head->prev = &mru_list; + mru_list.next->prev = new_head; + mru_list.next = new_head; + } + + // Inserts an item into the hash table OR combines it with an existing entry + // The new or modified item will be moved to the head of the MRU list. + void insert_hash(item* new_item) { + std::size_t hash = new_item->hash(); + std::size_t idx = hash & (table_size - 1); + for(item* bucket_entry = table[idx]; bucket_entry; bucket_entry = bucket_entry->next) { + if(traces_equal(new_item, bucket_entry)) { + ++bucket_entry->count; + move_to_head(&bucket_entry->mru); + return; + } + } + new_item->next = table[idx]; + new_item->bucket = idx; + new_item->count = 1; + table[idx] = new_item; + move_to_head(&new_item->mru); + } + + void flush_hash() { + for(std::size_t i = 0; i < table_size; ++i) { + for(item* entry = table[i]; entry; entry = entry->next) { + write(entry); + entry->bucket = 0xFFFFFFFF; + } + table[i] = nullptr; + } + } + + // The signal handler calling this must not use SA_NODEFER + void handle_tick(void** data, int count) { + item* entry = evict_oldest(); + + int out = 0; + // Translate addresses to wasm addresses; skip any frames that are outside wasm + for(int i = 0; i < count && out < max_frames; ++i) { + auto addr = addr_map.translate(data[i]); + if(addr != 0xFFFFFFFFu) { + if(out > 0) { + ++addr; + } + entry->frames[out++] = addr; + } + } + if(out != 0) { + entry->len = out; + if(out % 2 == 0) { + entry->frames[out] = 0; // so hashing can align to a 64-bit boundary + } + insert_hash(entry); + } + } + + int fd; + + char* outbuf; + std::size_t outpos; + std::size_t outsize; + + list_header mru_list; + item** table; + std::size_t table_size; // must be a power of 2 + + // Backend specific backtrace + const profile_instr_map& addr_map; + int (*get_backtrace_fn)(const void*, void**, int, void*); + const void* exec_context; + + template + void init_backtrace(const Context& context) { + get_backtrace_fn = [](const void* ctx, void** data, int len, void* uc) { + return static_cast(ctx)->backtrace(data, len, uc); + }; + exec_context = &context; + } + + // Unsafe to access from a signal handler + std::vector outbuf_storage; + std::vector items_storage; + std::vector table_storage; +}; + +inline void profile_handler(int sig, siginfo_t *info, void *); + +inline void register_profile_signal_handler_impl() { + struct sigaction sa; + sa.sa_sigaction = profile_handler; + sigemptyset(&sa.sa_mask); + sa.sa_flags = SA_SIGINFO; + sigaction(SIGPROF, &sa, nullptr); +} + +inline void register_profile_signal_handler() { + static int init_helper = (register_profile_signal_handler_impl(), 0); + ignore_unused_variable_warning(init_helper); +} + +// Sets the profile interval. Should only be called before starting any profiler. +// The interval should be between 1 and 999999. +inline void set_profile_interval_us(uint32_t value) { + profile_interval_us = value; +} + +#if USE_POSIX_TIMERS + +inline void profile_handler(int sig, siginfo_t *info, void * uc) { + static_assert(std::atomic::is_always_lock_free); + auto * ptr = std::atomic_load(static_cast*>(info->si_value.sival_ptr)); + if(ptr) { + int saved_errno = errno; + void* data[profile_data::max_frames*2]; // Includes both wasm and native frames + int count = ptr->get_backtrace_fn(ptr->exec_context, data, sizeof(data)/sizeof(data[0]), uc); + ptr->handle_tick(data, count); + errno = saved_errno; + } +} + +struct profile_manager { + profile_manager() { + register_profile_signal_handler(); + sigevent event; + event.sigev_notify = SIGEV_THREAD_ID; + event.sigev_signo = SIGPROF; + event.sigev_value.sival_ptr = ¤t_data; + event._sigev_un._tid = gettid(); + int res = timer_create(CLOCK_MONOTONIC, &event, &timer); + EOS_VM_ASSERT(res == 0, profile_exception, "Failed to start timer"); + struct itimerspec spec; + spec.it_interval.tv_sec = 0; + spec.it_interval.tv_nsec = profile_interval_us * 1000; + spec.it_value.tv_sec = 0; + spec.it_value.tv_nsec = profile_interval_us * 1000; + res = timer_settime(timer, 0, &spec, nullptr); + EOS_VM_ASSERT(res == 0, profile_exception, "Failed to start timer"); + } + void start(profile_data* data) { + EOS_VM_ASSERT(!current_data, profile_exception, "Already profiling in the current thread"); + current_data = data; + } + void stop() { + current_data = nullptr; + } + ~profile_manager() { + current_data = nullptr; + timer_delete(timer); + } + timer_t timer; + std::atomic current_data; +}; + +__attribute__((visibility("default"))) +inline thread_local std::unique_ptr per_thread_profile_manager; + +struct scoped_profile { + explicit scoped_profile(profile_data* data) { + if(data) { + if(!per_thread_profile_manager) { + per_thread_profile_manager = std::make_unique(); + } + per_thread_profile_manager->start(data); + } + } + ~scoped_profile() { + if(per_thread_profile_manager) { + per_thread_profile_manager->stop(); + } + } +}; + +#else + +__attribute__((visibility("default"))) +inline thread_local std::atomic per_thread_profile_data = ATOMIC_VAR_INIT(nullptr); + +inline void profile_handler(int sig, siginfo_t* info, void* uc) { + static_assert(std::atomic::is_always_lock_free); + auto * ptr = std::atomic_load(&per_thread_profile_data); + if(ptr) { + int saved_errno = errno; + void* data[profile_data::max_frames*2]; // Includes both wasm and native frames + int count = ptr->get_backtrace_fn(ptr->exec_context, data, sizeof(data)/sizeof(data[0]), uc); + ptr->handle_tick(data, count); + errno = saved_errno; + } +} + +struct profile_manager { + profile_manager() { + register_profile_signal_handler(); + timer_thread = std::thread([this]{ + auto lock = std::unique_lock(mutex); + while(!timer_cond.wait_for(lock, std::chrono::microseconds(profile_interval_us), [&]{ return done; })) { + for(pthread_t notify : threads_to_notify) { + pthread_kill(notify, SIGPROF); + } + } + }); + } + void start(profile_data* data) { + auto lock = std::lock_guard(mutex); + + per_thread_profile_data = data; + threads_to_notify.push_back(pthread_self()); + } + void stop() { + per_thread_profile_data = nullptr; + auto lock = std::lock_guard(mutex); + threads_to_notify.erase(std::find(threads_to_notify.begin(), threads_to_notify.end(), pthread_self())); + } + ~profile_manager() { + { + auto lock = std::scoped_lock(mutex); + done = true; + } + timer_cond.notify_one(); + timer_thread.join(); + } + static profile_manager& instance() { + static profile_manager result; + return result; + } + std::mutex mutex; + std::vector threads_to_notify; + + std::thread timer_thread; + bool done = false; + std::condition_variable timer_cond; +}; + +class scoped_profile { + public: + explicit scoped_profile(profile_data* data) : data(data) { + if(data) { + profile_manager::instance().start(data); + } + } + ~scoped_profile() { + if(data) { + profile_manager::instance().stop(); + } + } + private: + profile_data* data; +}; + +#endif + +} diff --git a/include/eosio/vm/signals.hpp b/include/eosio/vm/signals.hpp index 0ae917e0..3de07498 100644 --- a/include/eosio/vm/signals.hpp +++ b/include/eosio/vm/signals.hpp @@ -71,6 +71,10 @@ namespace eosio { namespace vm { caught_exception = true; } if (caught_exception) { + sigset_t block_mask; + sigemptyset(&block_mask); + sigaddset(&block_mask, SIGPROF); + pthread_sigmask(SIG_BLOCK, &block_mask, nullptr); sigjmp_buf* dest = std::atomic_load(&signal_dest); siglongjmp(*dest, -1); } @@ -79,6 +83,10 @@ namespace eosio { namespace vm { template [[noreturn]] inline void throw_(const char* msg) { saved_exception = std::make_exception_ptr(E{msg}); + sigset_t block_mask; + sigemptyset(&block_mask); + sigaddset(&block_mask, SIGPROF); + pthread_sigmask(SIG_BLOCK, &block_mask, nullptr); sigjmp_buf* dest = std::atomic_load(&signal_dest); siglongjmp(*dest, -1); } @@ -87,6 +95,7 @@ namespace eosio { namespace vm { struct sigaction sa; sa.sa_sigaction = &signal_handler; sigemptyset(&sa.sa_mask); + sigaddset(&sa.sa_mask, SIGPROF); sa.sa_flags = SA_NODEFER | SA_SIGINFO; sigaction(SIGSEGV, &sa, &prev_signal_handler); sigaction(SIGBUS, &sa, &prev_signal_handler); @@ -130,6 +139,7 @@ namespace eosio { namespace vm { sigaddset(&unblock_mask, SIGSEGV); sigaddset(&unblock_mask, SIGBUS); sigaddset(&unblock_mask, SIGFPE); + sigaddset(&unblock_mask, SIGPROF); pthread_sigmask(SIG_UNBLOCK, &unblock_mask, &old_sigmask); try { f(); diff --git a/include/eosio/vm/types.hpp b/include/eosio/vm/types.hpp index c04a0ab2..43e81653 100644 --- a/include/eosio/vm/types.hpp +++ b/include/eosio/vm/types.hpp @@ -149,6 +149,20 @@ namespace eosio { namespace vm { typedef std::uint32_t wasm_ptr_t; typedef std::uint32_t wasm_size_t; + struct name_assoc { + std::uint32_t idx; + guarded_vector name; + }; + struct indirect_name_assoc { + std::uint32_t idx; + guarded_vector namemap; + }; + struct name_section { + guarded_vector* module_name = nullptr; + guarded_vector* function_names = nullptr; + guarded_vector* local_names = nullptr; + }; + struct module { growable_allocator allocator = { constants::initial_module_size }; uint32_t start = std::numeric_limits::max(); @@ -163,6 +177,9 @@ namespace eosio { namespace vm { guarded_vector code = { allocator, 0 }; guarded_vector data = { allocator, 0 }; + // Custom sections: + name_section* names = nullptr; + // not part of the spec for WASM guarded_vector import_functions = { allocator, 0 }; guarded_vector type_aliases = { allocator, 0 }; diff --git a/include/eosio/vm/x86_64.hpp b/include/eosio/vm/x86_64.hpp index d6921f42..dc76ca17 100644 --- a/include/eosio/vm/x86_64.hpp +++ b/include/eosio/vm/x86_64.hpp @@ -50,7 +50,7 @@ namespace eosio { namespace vm { // emit host functions const uint32_t num_imported = mod.get_imported_functions_size(); - const std::size_t host_functions_size = 40 * num_imported; + const std::size_t host_functions_size = (40 + 10 * Context::async_backtrace()) * num_imported; _code_start = _mod.allocator.alloc(host_functions_size); _code_end = _code_start + host_functions_size; // code already set @@ -103,7 +103,7 @@ namespace eosio { namespace vm { void emit_prologue(const func_type& /*ft*/, const guarded_vector& locals, uint32_t funcnum) { _ft = &_mod.types[_mod.functions[funcnum]]; // FIXME: This is not a tight upper bound - const std::size_t instruction_size_ratio_upper_bound = use_softfloat?49:79; + const std::size_t instruction_size_ratio_upper_bound = use_softfloat?(Context::async_backtrace()?63:49):79; std::size_t code_size = max_prologue_size + _mod.code[funcnum].size * instruction_size_ratio_upper_bound + max_epilogue_size; _code_start = _mod.allocator.alloc(code_size); _code_end = _code_start + code_size; @@ -623,7 +623,8 @@ namespace eosio { namespace vm { } void emit_current_memory() { - auto icount = fixed_size_instr(17); + auto icount = variable_size_instr(17, 35); + emit_setup_backtrace(); // pushq %rdi emit_bytes(0x57); // pushq %rsi @@ -637,13 +638,15 @@ namespace eosio { namespace vm { emit_bytes(0x5e); // pop %rdi emit_bytes(0x5f); + emit_restore_backtrace(); // push %rax emit_bytes(0x50); } void emit_grow_memory() { - auto icount = fixed_size_instr(21); + auto icount = variable_size_instr(21, 39); // popq %rax emit_bytes(0x58); + emit_setup_backtrace(); // pushq %rdi emit_bytes(0x57); // pushq %rsi @@ -659,6 +662,7 @@ namespace eosio { namespace vm { emit_bytes(0x5e); // pop %rdi emit_bytes(0x5f); + emit_restore_backtrace(); // push %rax emit_bytes(0x50); } @@ -974,63 +978,63 @@ namespace eosio { namespace vm { // --------------- f32 relops ---------------------- void emit_f32_eq() { - auto icount = softfloat_instr(25,45); + auto icount = softfloat_instr(25,45,59); emit_f32_relop(0x00, CHOOSE_FN(_eosio_f32_eq), false, false); } void emit_f32_ne() { - auto icount = softfloat_instr(24,47); + auto icount = softfloat_instr(24,47,61); emit_f32_relop(0x00, CHOOSE_FN(_eosio_f32_eq), false, true); } void emit_f32_lt() { - auto icount = softfloat_instr(25,45); + auto icount = softfloat_instr(25,45,59); emit_f32_relop(0x01, CHOOSE_FN(_eosio_f32_lt), false, false); } void emit_f32_gt() { - auto icount = softfloat_instr(25,45); + auto icount = softfloat_instr(25,45,59); emit_f32_relop(0x01, CHOOSE_FN(_eosio_f32_lt), true, false); } void emit_f32_le() { - auto icount = softfloat_instr(25,45); + auto icount = softfloat_instr(25,45,59); emit_f32_relop(0x02, CHOOSE_FN(_eosio_f32_le), false, false); } void emit_f32_ge() { - auto icount = softfloat_instr(25,45); + auto icount = softfloat_instr(25,45,59); emit_f32_relop(0x02, CHOOSE_FN(_eosio_f32_le), true, false); } // --------------- f64 relops ---------------------- void emit_f64_eq() { - auto icount = softfloat_instr(25,47); + auto icount = softfloat_instr(25,47,61); emit_f64_relop(0x00, CHOOSE_FN(_eosio_f64_eq), false, false); } void emit_f64_ne() { - auto icount = softfloat_instr(24,49); + auto icount = softfloat_instr(24,49,63); emit_f64_relop(0x00, CHOOSE_FN(_eosio_f64_eq), false, true); } void emit_f64_lt() { - auto icount = softfloat_instr(25,47); + auto icount = softfloat_instr(25,47,61); emit_f64_relop(0x01, CHOOSE_FN(_eosio_f64_lt), false, false); } void emit_f64_gt() { - auto icount = softfloat_instr(25,47); + auto icount = softfloat_instr(25,47,61); emit_f64_relop(0x01, CHOOSE_FN(_eosio_f64_lt), true, false); } void emit_f64_le() { - auto icount = softfloat_instr(25,47); + auto icount = softfloat_instr(25,47,61); emit_f64_relop(0x02, CHOOSE_FN(_eosio_f64_le), false, false); } void emit_f64_ge() { - auto icount = softfloat_instr(25,47); + auto icount = softfloat_instr(25,47,61); emit_f64_relop(0x02, CHOOSE_FN(_eosio_f64_le), true, false); } @@ -1368,7 +1372,7 @@ namespace eosio { namespace vm { } void emit_f32_ceil() { - auto icount = softfloat_instr(12, 36); + auto icount = softfloat_instr(12, 36, 54); if constexpr (use_softfloat) { return emit_softfloat_unop(CHOOSE_FN(_eosio_f32_ceil)); } @@ -1379,7 +1383,7 @@ namespace eosio { namespace vm { } void emit_f32_floor() { - auto icount = softfloat_instr(12, 36); + auto icount = softfloat_instr(12, 36, 54); if constexpr (use_softfloat) { return emit_softfloat_unop(CHOOSE_FN(_eosio_f32_floor)); } @@ -1390,7 +1394,7 @@ namespace eosio { namespace vm { } void emit_f32_trunc() { - auto icount = softfloat_instr(12, 36); + auto icount = softfloat_instr(12, 36, 54); if constexpr (use_softfloat) { return emit_softfloat_unop(CHOOSE_FN(_eosio_f32_trunc)); } @@ -1401,7 +1405,7 @@ namespace eosio { namespace vm { } void emit_f32_nearest() { - auto icount = softfloat_instr(12, 36); + auto icount = softfloat_instr(12, 36, 54); if constexpr (use_softfloat) { return emit_softfloat_unop(CHOOSE_FN(_eosio_f32_nearest)); } @@ -1412,7 +1416,7 @@ namespace eosio { namespace vm { } void emit_f32_sqrt() { - auto icount = softfloat_instr(10, 36); + auto icount = softfloat_instr(10, 36, 54); if constexpr (use_softfloat) { return emit_softfloat_unop(CHOOSE_FN(_eosio_f32_sqrt)); } @@ -1425,23 +1429,23 @@ namespace eosio { namespace vm { // --------------- f32 binops ---------------------- void emit_f32_add() { - auto icount = softfloat_instr(21, 44); + auto icount = softfloat_instr(21, 44, 58); emit_f32_binop(0x58, CHOOSE_FN(_eosio_f32_add)); } void emit_f32_sub() { - auto icount = softfloat_instr(21, 44); + auto icount = softfloat_instr(21, 44, 58); emit_f32_binop(0x5c, CHOOSE_FN(_eosio_f32_sub)); } void emit_f32_mul() { - auto icount = softfloat_instr(21, 44); + auto icount = softfloat_instr(21, 44, 58); emit_f32_binop(0x59, CHOOSE_FN(_eosio_f32_mul)); } void emit_f32_div() { - auto icount = softfloat_instr(21, 44); + auto icount = softfloat_instr(21, 44, 58); emit_f32_binop(0x5e, CHOOSE_FN(_eosio_f32_div)); } void emit_f32_min() { - auto icount = softfloat_instr(47, 44); + auto icount = softfloat_instr(47, 44, 58); if constexpr(use_softfloat) { emit_f32_binop_softfloat(CHOOSE_FN(_eosio_f32_min)); return; @@ -1474,7 +1478,7 @@ namespace eosio { namespace vm { emit_bytes(0xf3, 0x0f, 0x11, 0x04, 0x24); } void emit_f32_max() { - auto icount = softfloat_instr(47, 44); + auto icount = softfloat_instr(47, 44, 58); if(use_softfloat) { emit_f32_binop_softfloat(CHOOSE_FN(_eosio_f32_max)); return; @@ -1554,7 +1558,7 @@ namespace eosio { namespace vm { } void emit_f64_ceil() { - auto icount = softfloat_instr(12, 38); + auto icount = softfloat_instr(12, 38, 56); if constexpr (use_softfloat) { return emit_softfloat_unop(CHOOSE_FN(_eosio_f64_ceil)); } @@ -1565,7 +1569,7 @@ namespace eosio { namespace vm { } void emit_f64_floor() { - auto icount = softfloat_instr(12, 38); + auto icount = softfloat_instr(12, 38, 56); if constexpr (use_softfloat) { return emit_softfloat_unop(CHOOSE_FN(_eosio_f64_floor)); } @@ -1576,7 +1580,7 @@ namespace eosio { namespace vm { } void emit_f64_trunc() { - auto icount = softfloat_instr(12, 38); + auto icount = softfloat_instr(12, 38, 56); if constexpr (use_softfloat) { return emit_softfloat_unop(CHOOSE_FN(_eosio_f64_trunc)); } @@ -1587,7 +1591,7 @@ namespace eosio { namespace vm { } void emit_f64_nearest() { - auto icount = softfloat_instr(12, 38); + auto icount = softfloat_instr(12, 38, 56); if constexpr (use_softfloat) { return emit_softfloat_unop(CHOOSE_FN(_eosio_f64_nearest)); } @@ -1598,7 +1602,7 @@ namespace eosio { namespace vm { } void emit_f64_sqrt() { - auto icount = softfloat_instr(10, 38); + auto icount = softfloat_instr(10, 38, 56); if constexpr (use_softfloat) { return emit_softfloat_unop(CHOOSE_FN(_eosio_f64_sqrt)); } @@ -1611,23 +1615,23 @@ namespace eosio { namespace vm { // --------------- f64 binops ---------------------- void emit_f64_add() { - auto icount = softfloat_instr(21, 47); + auto icount = softfloat_instr(21, 47, 61); emit_f64_binop(0x58, CHOOSE_FN(_eosio_f64_add)); } void emit_f64_sub() { - auto icount = softfloat_instr(21, 47); + auto icount = softfloat_instr(21, 47, 61); emit_f64_binop(0x5c, CHOOSE_FN(_eosio_f64_sub)); } void emit_f64_mul() { - auto icount = softfloat_instr(21, 47); + auto icount = softfloat_instr(21, 47, 61); emit_f64_binop(0x59, CHOOSE_FN(_eosio_f64_mul)); } void emit_f64_div() { - auto icount = softfloat_instr(21, 47); + auto icount = softfloat_instr(21, 47, 61); emit_f64_binop(0x5e, CHOOSE_FN(_eosio_f64_div)); } void emit_f64_min() { - auto icount = softfloat_instr(49, 47); + auto icount = softfloat_instr(49, 47, 61); if(use_softfloat) { emit_f64_binop_softfloat(CHOOSE_FN(_eosio_f64_min)); return; @@ -1660,7 +1664,7 @@ namespace eosio { namespace vm { emit_bytes(0xf2, 0x0f, 0x11, 0x04, 0x24); } void emit_f64_max() { - auto icount = softfloat_instr(49, 47); + auto icount = softfloat_instr(49, 47, 61); if(use_softfloat) { emit_f64_binop_softfloat(CHOOSE_FN(_eosio_f64_max)); return; @@ -1727,7 +1731,7 @@ namespace eosio { namespace vm { } void emit_i32_trunc_s_f32() { - auto icount = softfloat_instr(33, 36); + auto icount = softfloat_instr(33, 36, 54); if constexpr (use_softfloat) { return emit_softfloat_unop(CHOOSE_FN(softfloat_trap<&_eosio_f32_trunc_i32s>())); } @@ -1738,7 +1742,7 @@ namespace eosio { namespace vm { } void emit_i32_trunc_u_f32() { - auto icount = softfloat_instr(46, 36); + auto icount = softfloat_instr(46, 36, 54); if constexpr (use_softfloat) { return emit_softfloat_unop(CHOOSE_FN(softfloat_trap<&_eosio_f32_trunc_i32u>())); } @@ -1755,7 +1759,7 @@ namespace eosio { namespace vm { fix_branch(emit_branch_target32(), fpe_handler); } void emit_i32_trunc_s_f64() { - auto icount = softfloat_instr(34, 38); + auto icount = softfloat_instr(34, 38, 56); if constexpr (use_softfloat) { return emit_softfloat_unop(CHOOSE_FN(softfloat_trap<&_eosio_f64_trunc_i32s>())); } @@ -1766,7 +1770,7 @@ namespace eosio { namespace vm { } void emit_i32_trunc_u_f64() { - auto icount = softfloat_instr(47, 38); + auto icount = softfloat_instr(47, 38, 56); if constexpr (use_softfloat) { return emit_softfloat_unop(CHOOSE_FN(softfloat_trap<&_eosio_f64_trunc_i32u>())); } @@ -1794,7 +1798,7 @@ namespace eosio { namespace vm { void emit_i64_extend_u_i32() { /* Nothing to do */ } void emit_i64_trunc_s_f32() { - auto icount = softfloat_instr(35, 37); + auto icount = softfloat_instr(35, 37, 55); if constexpr (use_softfloat) { return emit_softfloat_unop(CHOOSE_FN(softfloat_trap<&_eosio_f32_trunc_i64s>())); } @@ -1804,7 +1808,7 @@ namespace eosio { namespace vm { emit_bytes(0x48, 0x89, 0x04 ,0x24); } void emit_i64_trunc_u_f32() { - auto icount = softfloat_instr(101, 37); + auto icount = softfloat_instr(101, 37, 55); if constexpr (use_softfloat) { return emit_softfloat_unop(CHOOSE_FN(softfloat_trap<&_eosio_f32_trunc_i64u>())); } @@ -1847,7 +1851,7 @@ namespace eosio { namespace vm { fix_branch(emit_branch_target32(), fpe_handler); } void emit_i64_trunc_s_f64() { - auto icount = softfloat_instr(35, 38); + auto icount = softfloat_instr(35, 38, 56); if constexpr (use_softfloat) { return emit_softfloat_unop(CHOOSE_FN(softfloat_trap<&_eosio_f64_trunc_i64s>())); } @@ -1857,7 +1861,7 @@ namespace eosio { namespace vm { emit_bytes(0x48, 0x89, 0x04 ,0x24); } void emit_i64_trunc_u_f64() { - auto icount = softfloat_instr(109, 38); + auto icount = softfloat_instr(109, 38, 56); if constexpr (use_softfloat) { return emit_softfloat_unop(CHOOSE_FN(softfloat_trap<&_eosio_f64_trunc_i64u>())); } @@ -1901,7 +1905,7 @@ namespace eosio { namespace vm { } void emit_f32_convert_s_i32() { - auto icount = softfloat_instr(10, 36); + auto icount = softfloat_instr(10, 36, 54); if constexpr (use_softfloat) { return emit_softfloat_unop(CHOOSE_FN(_eosio_i32_to_f32)); } @@ -1911,7 +1915,7 @@ namespace eosio { namespace vm { emit_bytes(0xf3, 0x0f, 0x11, 0x04, 0x24); } void emit_f32_convert_u_i32() { - auto icount = softfloat_instr(11, 36); + auto icount = softfloat_instr(11, 36, 54); if constexpr (use_softfloat) { return emit_softfloat_unop(CHOOSE_FN(_eosio_ui32_to_f32)); } @@ -1922,7 +1926,7 @@ namespace eosio { namespace vm { emit_bytes(0xf3, 0x0f, 0x11, 0x04, 0x24); } void emit_f32_convert_s_i64() { - auto icount = softfloat_instr(11, 38); + auto icount = softfloat_instr(11, 38, 56); if constexpr (use_softfloat) { return emit_softfloat_unop(CHOOSE_FN(_eosio_i64_to_f32)); } @@ -1932,7 +1936,7 @@ namespace eosio { namespace vm { emit_bytes(0xf3, 0x0f, 0x11, 0x04, 0x24); } void emit_f32_convert_u_i64() { - auto icount = softfloat_instr(55, 38); + auto icount = softfloat_instr(55, 38, 56); if constexpr (use_softfloat) { return emit_softfloat_unop(CHOOSE_FN(_eosio_ui64_to_f32)); } @@ -1972,7 +1976,7 @@ namespace eosio { namespace vm { emit_bytes(0xf3, 0x0f, 0x11, 0x04, 0x24); } void emit_f32_demote_f64() { - auto icount = softfloat_instr(16, 38); + auto icount = softfloat_instr(16, 38, 56); if constexpr (use_softfloat) { return emit_softfloat_unop(CHOOSE_FN(_eosio_f64_demote)); } @@ -1987,7 +1991,7 @@ namespace eosio { namespace vm { emit_bytes(0x89, 0x44, 0x24, 0x04); } void emit_f64_convert_s_i32() { - auto icount = softfloat_instr(10, 37); + auto icount = softfloat_instr(10, 37, 55); if constexpr (use_softfloat) { return emit_softfloat_unop(CHOOSE_FN(_eosio_i32_to_f64)); } @@ -1997,7 +2001,7 @@ namespace eosio { namespace vm { emit_bytes(0xf2, 0x0f, 0x11, 0x04, 0x24); } void emit_f64_convert_u_i32() { - auto icount = softfloat_instr(11, 37); + auto icount = softfloat_instr(11, 37, 55); if constexpr (use_softfloat) { return emit_softfloat_unop(CHOOSE_FN(_eosio_ui32_to_f64)); } @@ -2007,7 +2011,7 @@ namespace eosio { namespace vm { emit_bytes(0xf2, 0x0f, 0x11, 0x04, 0x24); } void emit_f64_convert_s_i64() { - auto icount = softfloat_instr(11, 38); + auto icount = softfloat_instr(11, 38, 56); if constexpr (use_softfloat) { return emit_softfloat_unop(CHOOSE_FN(_eosio_i64_to_f64)); } @@ -2017,7 +2021,7 @@ namespace eosio { namespace vm { emit_bytes(0xf2, 0x0f, 0x11, 0x04, 0x24); } void emit_f64_convert_u_i64() { - auto icount = softfloat_instr(49, 38); + auto icount = softfloat_instr(49, 38, 56); if constexpr (use_softfloat) { return emit_softfloat_unop(CHOOSE_FN(_eosio_ui64_to_f64)); } @@ -2053,7 +2057,7 @@ namespace eosio { namespace vm { emit_bytes(0xf2, 0x0f, 0x11, 0x04, 0x24); } void emit_f64_promote_f32() { - auto icount = softfloat_instr(10, 37); + auto icount = softfloat_instr(10, 37, 55); if constexpr (use_softfloat) { return emit_softfloat_unop(CHOOSE_FN(_eosio_f32_promote)); } @@ -2094,6 +2098,13 @@ namespace eosio { namespace vm { body.jit_code_offset = _code_start - (unsigned char*)_code_segment_base; } + // returns the current write address + const void* get_addr() const { + return code; + } + + const void* get_base_addr() const { return _code_segment_base; } + private: auto fixed_size_instr(std::size_t expected_bytes) { @@ -2112,8 +2123,8 @@ namespace eosio { namespace vm { ignore_unused_variable_warning(code, min_code, max_code); }}; } - auto softfloat_instr(std::size_t hard_expected, std::size_t soft_expected) { - return fixed_size_instr(use_softfloat?soft_expected:hard_expected); + auto softfloat_instr(std::size_t hard_expected, std::size_t soft_expected, std::size_t softbt_expected) { + return fixed_size_instr(use_softfloat?(Context::async_backtrace()?softbt_expected:soft_expected):hard_expected); } module& _mod; @@ -2266,16 +2277,17 @@ namespace eosio { namespace vm { template void emit_softfloat_unop(T(*softfloatfun)(U)) { + auto extra = emit_setup_backtrace(); // pushq %rdi emit_bytes(0x57); // pushq %rsi emit_bytes(0x56); if constexpr(sizeof(U) == 4) { // movq 16(%rsp), %edi - emit_bytes(0x8b, 0x7c, 0x24, 0x10); + emit_bytes(0x8b, 0x7c, 0x24, 0x10 + extra); } else { // movq 16(%rsp), %rdi - emit_bytes(0x48, 0x8b, 0x7c, 0x24, 0x10); + emit_bytes(0x48, 0x8b, 0x7c, 0x24, 0x10 + extra); } emit_align_stack(); // movabsq $softfloatfun, %rax @@ -2288,6 +2300,7 @@ namespace eosio { namespace vm { emit_bytes(0x5e); // popq %rdi emit_bytes(0x5f); + emit_restore_backtrace(); if constexpr(sizeof(T) == 4) { static_assert(sizeof(U) == 4, "Can only push 4-byte item if the upper 4 bytes are already 0"); // movq %eax, (%rsp) @@ -2299,14 +2312,15 @@ namespace eosio { namespace vm { } void emit_f32_binop_softfloat(float32_t (*softfloatfun)(float32_t, float32_t)) { + auto extra = emit_setup_backtrace(); // pushq %rdi emit_bytes(0x57); // pushq %rsi emit_bytes(0x56); // movq 16(%rsp), %esi - emit_bytes(0x8b, 0x74, 0x24, 0x10); + emit_bytes(0x8b, 0x74, 0x24, 0x10 + extra); // movq 24(%rsp), %edi - emit_bytes(0x8b, 0x7c, 0x24, 0x18); + emit_bytes(0x8b, 0x7c, 0x24, 0x18 + extra); emit_align_stack(); // movabsq $softfloatfun, %rax emit_bytes(0x48, 0xb8); @@ -2318,21 +2332,23 @@ namespace eosio { namespace vm { emit_bytes(0x5e); // popq %rdi emit_bytes(0x5f); + emit_restore_backtrace_basic(); // addq $8, %rsp - emit_bytes(0x48, 0x83, 0xc4, 0x08); + emit_bytes(0x48, 0x83, 0xc4, 0x08 + extra); // movq %eax, (%rsp) emit_bytes(0x89, 0x04, 0x24); } void emit_f64_binop_softfloat(float64_t (*softfloatfun)(float64_t, float64_t)) { + auto extra = emit_setup_backtrace(); // pushq %rdi emit_bytes(0x57); // pushq %rsi emit_bytes(0x56); // movq 16(%rsp), %rsi - emit_bytes(0x48, 0x8b, 0x74, 0x24, 0x10); + emit_bytes(0x48, 0x8b, 0x74, 0x24, 0x10 + extra); // movq 24(%rsp), %rdi - emit_bytes(0x48, 0x8b, 0x7c, 0x24, 0x18); + emit_bytes(0x48, 0x8b, 0x7c, 0x24, 0x18 + extra); emit_align_stack(); // movabsq $softfloatfun, %rax emit_bytes(0x48, 0xb8); @@ -2344,28 +2360,30 @@ namespace eosio { namespace vm { emit_bytes(0x5e); // popq %rdi emit_bytes(0x5f); + emit_restore_backtrace_basic(); // addq $8, %rsp - emit_bytes(0x48, 0x83, 0xc4, 0x08); + emit_bytes(0x48, 0x83, 0xc4, 0x08 + extra); // movq %rax, (%rsp) emit_bytes(0x48, 0x89, 0x04, 0x24); } void emit_f32_relop(uint8_t opcode, uint64_t (*softfloatfun)(float32_t, float32_t), bool switch_params, bool flip_result) { if constexpr (use_softfloat) { + auto extra = emit_setup_backtrace(); // pushq %rdi emit_bytes(0x57); // pushq %rsi emit_bytes(0x56); if(switch_params) { // movq 24(%rsp), %esi - emit_bytes(0x8b, 0x74, 0x24, 0x18); + emit_bytes(0x8b, 0x74, 0x24, 0x18 + extra); // movq 16(%rsp), %edi - emit_bytes(0x8b, 0x7c, 0x24, 0x10); + emit_bytes(0x8b, 0x7c, 0x24, 0x10 + extra); } else { // movq 16(%rsp), %esi - emit_bytes(0x8b, 0x74, 0x24, 0x10); + emit_bytes(0x8b, 0x74, 0x24, 0x10 + extra); // movq 24(%rsp), %edi - emit_bytes(0x8b, 0x7c, 0x24, 0x18); + emit_bytes(0x8b, 0x7c, 0x24, 0x18 + extra); } emit_align_stack(); // movabsq $softfloatfun, %rax @@ -2378,12 +2396,13 @@ namespace eosio { namespace vm { emit_bytes(0x5e); // popq %rdi emit_bytes(0x5f); + emit_restore_backtrace_basic(); if (flip_result) { // xor $0x1, %al emit_bytes(0x34, 0x01); } // addq $8, %rsp - emit_bytes(0x48, 0x83, 0xc4, 0x08); + emit_bytes(0x48, 0x83, 0xc4, 0x08 + extra); // movq %rax, (%rsp) emit_bytes(0x48, 0x89, 0x04, 0x24); } else { @@ -2417,20 +2436,21 @@ namespace eosio { namespace vm { void emit_f64_relop(uint8_t opcode, uint64_t (*softfloatfun)(float64_t, float64_t), bool switch_params, bool flip_result) { if constexpr (use_softfloat) { + auto extra = emit_setup_backtrace(); // pushq %rdi emit_bytes(0x57); // pushq %rsi emit_bytes(0x56); if(switch_params) { // movq 24(%rsp), %rsi - emit_bytes(0x48, 0x8b, 0x74, 0x24, 0x18); + emit_bytes(0x48, 0x8b, 0x74, 0x24, 0x18 + extra); // movq 16(%rsp), %rdi - emit_bytes(0x48, 0x8b, 0x7c, 0x24, 0x10); + emit_bytes(0x48, 0x8b, 0x7c, 0x24, 0x10 + extra); } else { // movq 16(%rsp), %rsi - emit_bytes(0x48, 0x8b, 0x74, 0x24, 0x10); + emit_bytes(0x48, 0x8b, 0x74, 0x24, 0x10 + extra); // movq 24(%rsp), %rdi - emit_bytes(0x48, 0x8b, 0x7c, 0x24, 0x18); + emit_bytes(0x48, 0x8b, 0x7c, 0x24, 0x18 + extra); } emit_align_stack(); // movabsq $softfloatfun, %rax @@ -2443,12 +2463,13 @@ namespace eosio { namespace vm { emit_bytes(0x5e); // popq %rdi emit_bytes(0x5f); + emit_restore_backtrace_basic(); if (flip_result) { // xor $0x1, %al emit_bytes(0x34, 0x01); } // addq $8, %rsp - emit_bytes(0x48, 0x83, 0xc4, 0x08); + emit_bytes(0x48, 0x83, 0xc4, 0x08 + extra); // movq %rax, (%rsp) emit_bytes(0x48, 0x89, 0x04, 0x24); } else { @@ -2579,6 +2600,14 @@ namespace eosio { namespace vm { } void emit_host_call(uint32_t funcnum) { + uint32_t extra = 0; + if constexpr (Context::async_backtrace()) { + // pushq %rbp + emit_bytes(0x55); + // movq %rsp, (%rdi) + emit_bytes(0x48, 0x89, 0x27); + extra = 8; + } // mov $funcnum, %edx emit_bytes(0xba); emit_operand32(funcnum); @@ -2587,7 +2616,7 @@ namespace eosio { namespace vm { // pushq %rsi emit_bytes(0x56); // lea 24(%rsp), %rsi - emit_bytes(0x48, 0x8d, 0x74, 0x24, 0x18); + emit_bytes(0x48, 0x8d, 0x74, 0x24, 0x18 + extra); emit_align_stack(); // movabsq $call_host_function, %rax emit_bytes(0x48, 0xb8); @@ -2599,10 +2628,49 @@ namespace eosio { namespace vm { emit_bytes(0x5e); // popq %rdi emit_bytes(0x5f); + if constexpr (Context::async_backtrace()) { + emit_restore_backtrace_basic(); + // popq %rbp + emit_bytes(0x5d); + } // retq emit_bytes(0xc3); } + // Needs to run before saving %rdi. Returns the number of bytes pushed onto the stack. + uint32_t emit_setup_backtrace() { + if constexpr (Context::async_backtrace()) { + // callq next + emit_bytes(0xe8); + emit_operand32(0); + // next: + // pushq %rbp + emit_bytes(0x55); + // movq %rsp, (%rdi) + emit_bytes(0x48, 0x89, 0x27); + return 16; + } else { + return 0; + } + } + // Does not adjust the stack pointer. Use this if the + // stack pointer adjustment is combined with another instruction. + void emit_restore_backtrace_basic() { + if constexpr (Context::async_backtrace()) { + // xorl %edx, %edx + emit_bytes(0x31, 0xd2); + // movq %rdx, (%rdi) + emit_bytes(0x48, 0x89, 0x17); + } + } + void emit_restore_backtrace() { + if constexpr (Context::async_backtrace()) { + emit_restore_backtrace_basic(); + // addq $16, %rsp + emit_bytes(0x48, 0x83, 0xc4, 0x10); + } + } + bool is_host_function(uint32_t funcnum) { return funcnum < _mod.get_imported_functions_size(); } static native_value call_host_function(Context* context /*rdi*/, native_value* stack /*rsi*/, uint32_t idx /*edx*/) { diff --git a/tools/CMakeLists.txt b/tools/CMakeLists.txt index 7dba7381..85fb5b35 100644 --- a/tools/CMakeLists.txt +++ b/tools/CMakeLists.txt @@ -9,3 +9,6 @@ target_link_libraries(bench-interp eos-vm) add_executable(hello-driver ${CMAKE_CURRENT_SOURCE_DIR}/hello_driver.cpp) target_link_libraries(hello-driver eos-vm) + +add_executable(eos-vm-addr2line addr2line.cpp) +target_link_libraries(eos-vm-addr2line eos-vm) diff --git a/tools/addr2line.cpp b/tools/addr2line.cpp new file mode 100644 index 00000000..f26d773d --- /dev/null +++ b/tools/addr2line.cpp @@ -0,0 +1,134 @@ +#include +#include +#include +#include + +struct nm_debug_info { + using builder = nm_debug_info; + void on_code_start(const void* compiled_base, const void* wasm_code_start) { + wasm_base = wasm_code_start; + } + void on_function_start(const void* code_addr, const void* wasm_addr) { + function_offsets.push_back(static_cast(reinterpret_cast(wasm_addr) - reinterpret_cast(wasm_base))); + } + void on_instr_start(const void* code_addr, const void* wasm_addr) {} + void on_code_end(const void* code_addr, const void* wasm_addr) {} + void set(nm_debug_info&& other) { *this = std::move(other); } + void relocate(const void*) {} + + uint32_t get_function(std::uint32_t addr) { + auto pos = std::lower_bound(function_offsets.begin(), function_offsets.end(), addr + 1); + if(pos == function_offsets.begin()) return 0; + return (pos - function_offsets.begin()) - 1; + } + + const void* wasm_base; + std::vector function_offsets; +}; + +eosio::vm::guarded_vector* find_export_name(eosio::vm::module& mod, uint32_t idx) { + if(mod.names && mod.names->function_names) { + for(uint32_t i = 0; i < mod.names->function_names->size(); ++i) { + if((*mod.names->function_names)[i].idx == idx) { + return &(*mod.names->function_names)[i].name; + } + } + } + for(uint32_t i = 0; i < mod.exports.size(); ++i) { + if(mod.exports[i].index == idx && mod.exports[i].kind == eosio::vm::Function) { + return &mod.exports[i].field_str; + } + } + return nullptr; +} + +struct nm_options { + static constexpr bool parse_custom_section_name = true; +}; + +bool starts_with(std::string_view arg, std::string_view prefix) { + return arg.substr(0, prefix.size()) == prefix; +} + +int main(int argc, const char** argv) { + bool print_addresses = false; + bool print_functions = false; + bool pretty = false; + std::vector addresses; + std::string filename = "a.out"; + for(int i = 1; i < argc; ++i) { + std::string arg = argv[i]; + if(arg == "-h" || arg == "--help") { + std::cerr << "Usage: " << argv[0] << " [-h] [-a] [-C] [-e wasm] [-f] [-s] [-i] [-p] address..." << std::endl; + return 0; + } else if(arg == "-a" || arg == "--addresses") { + print_addresses = true; + } else if(arg == "-C" || starts_with(arg, "--demangle=")) { + // Ignore requests to demangle + } else if(arg == "-e") { + if(++i < argc) { + filename = argv[i]; + } else { + std::cerr << "Missing argument to -e" << std::endl; + return 2; + } + } else if(starts_with(arg, "--exe=")) { + filename = arg.substr(6); + } else if(arg == "-f" || arg == "--functions") { + print_functions = true; + } else if(arg == "-s" || arg == "--basenames") { + // No effect without filenames + } else if(arg == "-i" || arg == "--inlines") { + // No effect without source information + } else if(arg == "-p" || arg == "--pretty-print") { + pretty = true; + } else if(arg[0] != '-') { + addresses.push_back(arg); + } else { + std::cerr << "unexpected argument: " << arg << std::endl; + return 2; + } + } + + std::vector function_names; + + using namespace eosio::vm; + auto code = read_wasm(filename); + nm_debug_info info; + module mod; + binary_parser parser(mod.allocator); + parser.parse_module(code, mod, info); + { + for(std::size_t i = 0; i < info.function_offsets.size(); ++i) { + if(guarded_vector* name = find_export_name(mod, i + mod.get_imported_functions_size())) { + function_names.push_back(std::string(reinterpret_cast(name->raw()), name->size())); + } else { + function_names.push_back("fn" + std::to_string( i + mod.get_imported_functions_size())); + } + } + } + + std::string_view sep = pretty ? " " : "\n"; + + auto print_one_address = [&](const std::string& addr) { + unsigned x = std::stoul(addr, nullptr, 16); + if(print_addresses) { + std::cout << std::showbase << std::hex << x << sep; + } + if(print_functions) { + std::cout << std::hex << function_names[info.get_function(x)] << sep; + } + std::cout << "??:0\n"; + }; + + if(!addresses.empty()) { + for(const auto& addr : addresses) { + print_one_address(addr); + } + } else { + std::string addr; + while(std::cin >> addr) { + print_one_address(addr); + } + } +} diff --git a/tools/interp.cpp b/tools/interp.cpp index 80c338b6..357d445e 100644 --- a/tools/interp.cpp +++ b/tools/interp.cpp @@ -1,6 +1,7 @@ #include #include #include +#include #include @@ -19,14 +20,26 @@ int main(int argc, char** argv) { return -1; } + bool profile = false; + std::string filename; + + if(argv[1] == std::string("-p")) { + profile = true; + filename = argv[2]; + } else { + filename = argv[1]; + } + watchdog wd{std::chrono::seconds(3)}; try { // Read the wasm into memory. - auto code = read_wasm( argv[1] ); + auto code = read_wasm( filename ); // Instaniate a new backend using the wasm provided. - backend bkend( code, &wa ); + backend bkend( code, &wa ); + auto prof = profile? std::make_unique("profile.out", bkend) : nullptr; + scoped_profile profile_runner(prof.get()); // Execute any exported functions provided by the wasm. bkend.execute_all(wd);