| /* |
| * Copyright (c) 2008, 2022, Oracle and/or its affiliates. All rights reserved. |
| * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. |
| * |
| * The Universal Permissive License (UPL), Version 1.0 |
| * |
| * Subject to the condition set forth below, permission is hereby granted to |
| * any person obtaining a copy of this software, associated documentation |
| * and/or data (collectively the "Software"), free of charge and under any |
| * and all copyright rights in the Software, and any and all patent rights |
| * owned or freely licensable by each licensor hereunder covering either (i) |
| * the unmodified Software as contributed to or provided by such licensor, |
| * or (ii) the Larger Works (as defined below), to deal in both |
| * |
| * (a) the Software, and |
| * |
| * (b) any piece of software and/or hardware listed in the lrgrwrks.txt file |
| * if one is included with the Software (each a "Larger Work" to which the |
| * Software is contributed by such licensors), |
| * |
| * without restriction, including without limitation the rights to copy, |
| * create derivative works of, display, perform, and distribute the Software |
| * and make, use, sell, offer for sale, import, export, have made, and have |
| * sold the Software and the Larger Work(s), and to sublicense the foregoing |
| * rights on either these or other terms. |
| * |
| * This license is subject to the following condition: |
| * |
| * The above copyright notice and either this complete permission notice or |
| * at a minimum a reference to the UPL must be included in all copies or |
| * substantial portions of the Software. |
| * |
| * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS |
| * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF |
| * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN |
| * NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, |
| * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR |
| * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE |
| * USE OR OTHER DEALINGS IN THE SOFTWARE. |
| * |
| * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA |
| * or visit www.oracle.com if you need additional information or have any |
| * questions. |
| * |
| */ |
| |
| /* hsdis.cpp -- dump a range of addresses as native instructions |
| This implements the plugin protocol required by the |
| HotSpot PrintAssembly option. |
| */ |
| |
| #include <stdlib.h> |
| #include <stdio.h> |
| #include <errno.h> |
| #include <inttypes.h> |
| #include <string.h> |
| |
| #include <llvm-c/Disassembler.h> |
| #include <llvm-c/DisassemblerTypes.h> |
| #include <llvm-c/Target.h> |
| #include <llvm-c/TargetMachine.h> |
| |
| #include "hsdis.h" |
| |
| /* short names for stuff in hsdis.h */ |
| typedef decode_instructions_event_callback_ftype event_callback_t; |
| typedef decode_instructions_printf_callback_ftype printf_callback_t; |
| |
| class hsdis_backend_base { |
| protected: |
| uintptr_t _start_va; |
| uintptr_t _end_va; |
| unsigned char* _buffer; |
| uintptr_t _length; |
| event_callback_t _event_callback; |
| void* _event_stream; |
| printf_callback_t _printf_callback; |
| void* _printf_stream; |
| int _do_newline; |
| |
| bool _losing; |
| const char* _arch_name; |
| |
| virtual void print_help(const char* msg, const char* arg) = 0; |
| virtual void print_insns_config() = 0; |
| virtual size_t decode_instruction(uintptr_t p, uintptr_t start, uintptr_t end) = 0; |
| virtual const char* format_insn_close(const char* close, char* buf, size_t bufsize) = 0; |
| |
| private: |
| /* ignore all events, return a null */ |
| static void* null_event_callback(void* ignore_stream, const char* ignore_event, void* arg) { |
| return NULL; |
| } |
| |
| /* print all events as XML markup */ |
| static void* xml_event_callback(void* stream, const char* event, void* arg) { |
| FILE* fp = (FILE*) stream; |
| #define NS_PFX "dis:" |
| if (event[0] != '/') { |
| /* issue the tag, with or without a formatted argument */ |
| fprintf(fp, "<" NS_PFX); |
| fprintf(fp, event, arg); |
| fprintf(fp, ">"); |
| } else { |
| ++event; /* skip slash */ |
| const char* argp = strchr(event, ' '); |
| if (argp == NULL) { |
| /* no arguments; just issue the closing tag */ |
| fprintf(fp, "</" NS_PFX "%s>", event); |
| } else { |
| /* split out the closing attributes as <dis:foo_done attr='val'/> */ |
| size_t event_prefix =(argp - event); |
| fprintf(fp, "<" NS_PFX "%.*s_done", (int) event_prefix, event); |
| fprintf(fp, argp, arg); |
| fprintf(fp, "/></" NS_PFX "%.*s>", (int) event_prefix, event); |
| } |
| } |
| #undef NS_PFX |
| return NULL; |
| } |
| |
| protected: |
| hsdis_backend_base(uintptr_t start_va, uintptr_t end_va, |
| unsigned char* buffer, uintptr_t length, |
| event_callback_t event_callback, void* event_stream, |
| printf_callback_t printf_callback, void* printf_stream, |
| int do_newline) : |
| _start_va(start_va), _end_va(end_va), |
| _buffer(buffer), _length(length), |
| _event_callback(event_callback), _event_stream(event_stream), |
| _printf_callback(printf_callback), _printf_stream(printf_stream), |
| _do_newline(do_newline), |
| _losing(false), _arch_name(NULL) |
| { |
| /* Make reasonable defaults for null callbacks. |
| A non-null stream for a null callback is assumed to be a FILE* for output. |
| Events are rendered as XML. |
| */ |
| if (_printf_callback == NULL) { |
| int (*fprintf_callback)(FILE*, const char*, ...) = &fprintf; |
| FILE* fprintf_stream = stdout; |
| _printf_callback = (printf_callback_t) fprintf_callback; |
| if (_printf_stream == NULL) |
| _printf_stream = (void*) fprintf_stream; |
| } |
| if (_event_callback == NULL) { |
| if (_event_stream == NULL) |
| _event_callback = (event_callback_t)&null_event_callback; |
| else |
| _event_callback = (event_callback_t)&xml_event_callback; |
| } |
| } |
| |
| public: |
| void* decode() { |
| uintptr_t start = _start_va; |
| uintptr_t end = _end_va; |
| uintptr_t p = start; |
| |
| (*_event_callback)(_event_stream, "insns", (void*)start); |
| |
| print_insns_config(); |
| |
| while (p < end && !_losing) { |
| (*_event_callback)(_event_stream, "insn", (void*) p); |
| |
| size_t size = decode_instruction(p, start, end); |
| if (size > 0) p += size; |
| else _losing = true; |
| |
| if (!_losing) { |
| char buf[128]; |
| const char* insn_close = format_insn_close("/insn", buf, sizeof(buf)); |
| (*_event_callback)(_event_stream, insn_close, (void*) p); |
| |
| if (_do_newline) { |
| /* follow each complete insn by a nice newline */ |
| (*_printf_callback)(_printf_stream, "\n"); |
| } |
| } |
| } |
| |
| if (_losing) (*_event_callback)(_event_stream, "/insns", (void*) p); |
| return (void*) p; |
| } |
| }; |
| |
| |
| class hsdis_backend : public hsdis_backend_base { |
| private: |
| LLVMDisasmContextRef _dcontext; |
| char _target_triple[128]; |
| |
| void parse_caller_options(const char* options) { |
| memset(&_target_triple, 0, sizeof(_target_triple)); |
| const char* p; |
| for (p = options; p != NULL; ) { |
| const char* q = strchr(p, ','); |
| size_t plen = (q == NULL) ? strlen(p) : ((q++) - p); |
| if (plen == 4 && strncmp(p, "help", plen) == 0) { |
| print_help(NULL, NULL); |
| } else if (plen > 6 && strncmp(p, "hsdis-", 6) == 0) { |
| // do not pass these to the next level |
| } else if (plen >= 14 && strncmp(p, "target_triple=", 14) == 0) { |
| char* target_triple = _target_triple; |
| size_t target_triple_size = sizeof(_target_triple); |
| target_triple_size -= 1; /*leave room for the null*/ |
| if (plen > target_triple_size) plen = target_triple_size; |
| strncpy(target_triple, p, plen); |
| target_triple[plen] = '\0'; |
| } |
| p = q; |
| } |
| } |
| |
| const char* native_target_triple() { |
| return LLVM_DEFAULT_TRIPLET; |
| } |
| |
| public: |
| hsdis_backend(uintptr_t start_va, uintptr_t end_va, |
| unsigned char* buffer, uintptr_t length, |
| event_callback_t event_callback, void* event_stream, |
| printf_callback_t printf_callback, void* printf_stream, |
| const char* options, int newline) |
| : hsdis_backend_base(start_va, end_va, |
| buffer, length, |
| event_callback, event_stream, |
| printf_callback, printf_stream, |
| newline), |
| _dcontext(NULL) { |
| /* Look into _options for anything interesting. */ |
| if (options != NULL) |
| parse_caller_options(options); |
| |
| /* Discover which architecture we are going to disassemble. */ |
| _arch_name = &_target_triple[0]; |
| if (_arch_name[0] == '\0') |
| _arch_name = native_target_triple(); |
| |
| if (LLVMInitializeNativeTarget() != 0) { |
| static bool complained = false; |
| if (!complained) |
| (*_printf_callback)(_printf_stream, "failed to initialize LLVM native target\n"); |
| complained = true; |
| /* must bail out */ |
| _losing = true; |
| return; |
| } |
| if (LLVMInitializeNativeAsmPrinter() != 0) { |
| static bool complained = false; |
| if (!complained) |
| (*_printf_callback)(_printf_stream, "failed to initialize LLVM native asm printer\n"); |
| complained = true; |
| /* must bail out */ |
| _losing = true; |
| return; |
| } |
| if (LLVMInitializeNativeDisassembler() != 0) { |
| static bool complained = false; |
| if (!complained) |
| (*_printf_callback)(_printf_stream, "failed to initialize LLVM native disassembler\n"); |
| complained = true; |
| /* must bail out */ |
| _losing = true; |
| return; |
| } |
| if ((_dcontext = LLVMCreateDisasm(_arch_name, NULL, 0, NULL, NULL)) == NULL) { |
| static bool complained = false; |
| const char* bad = _arch_name; |
| if (bad == &_target_triple[0]) |
| print_help("bad target_triple=%s", bad); |
| else if (!complained) |
| print_help("bad native target_triple=%s; please port hsdis to this platform", bad); |
| complained = true; |
| /* must bail out */ |
| _losing = true; |
| return; |
| } |
| |
| LLVMSetDisasmOptions(_dcontext, LLVMDisassembler_Option_PrintImmHex); |
| } |
| |
| ~hsdis_backend() { |
| if (_dcontext != NULL) { |
| LLVMDisasmDispose(_dcontext); |
| } |
| } |
| |
| protected: |
| virtual void print_help(const char* msg, const char* arg) { |
| if (msg != NULL) { |
| (*_printf_callback)(_printf_stream, "hsdis: "); |
| (*_printf_callback)(_printf_stream, msg, arg); |
| (*_printf_callback)(_printf_stream, "\n"); |
| } |
| (*_printf_callback)(_printf_stream, "hsdis output options:\n"); |
| (*_printf_callback)(_printf_stream, " target_triple=<triple> select disassembly target\n"); |
| (*_printf_callback)(_printf_stream, " help print this message\n"); |
| } |
| |
| virtual void print_insns_config() { |
| (*_event_callback)(_event_stream, "target_triple name='%s'", |
| (void*) _arch_name); |
| } |
| |
| virtual size_t decode_instruction(uintptr_t p, uintptr_t start, uintptr_t end) { |
| char buf[128]; |
| size_t size = LLVMDisasmInstruction(_dcontext, (uint8_t*)p, (uint64_t)(end - start), (uint64_t)p, buf, sizeof(buf)); |
| if (size > 0) { |
| (*_printf_callback)(_printf_stream, "%s", buf); |
| } else { |
| // LLVM encountered an unknown instruction |
| if (end - start >= 4) { |
| // Print the following word and skip past it |
| snprintf(buf, sizeof(buf), "\t.inst\t#0x%08x ; undefined", *(uint32_t*)p); |
| size = 4; |
| } else { |
| snprintf(buf, sizeof(buf), "\t<invalid instruction, aborting hsdis>"); |
| } |
| } |
| return size; |
| } |
| |
| virtual const char* format_insn_close(const char* close, char* buf, size_t bufsize) { |
| return close; |
| } |
| }; |
| |
| |
| void* decode_instructions_virtual(uintptr_t start_va, uintptr_t end_va, |
| unsigned char* buffer, uintptr_t length, |
| event_callback_t event_callback_arg, void* event_stream_arg, |
| printf_callback_t printf_callback_arg, void* printf_stream_arg, |
| const char* options, int newline) { |
| return hsdis_backend(start_va, end_va, |
| buffer, length, |
| event_callback_arg, event_stream_arg, |
| printf_callback_arg, printf_stream_arg, |
| options, newline == 0 ? false : true) |
| .decode(); |
| } |
| |
| /* This is the compatability interface for older version of hotspot */ |
| void* decode_instructions(void* start_pv, void* end_pv, |
| event_callback_t event_callback_arg, void* event_stream_arg, |
| printf_callback_t printf_callback_arg, void* printf_stream_arg, |
| const char* options) { |
| return decode_instructions_virtual((uintptr_t)start_pv, |
| (uintptr_t)end_pv, |
| (unsigned char*)start_pv, |
| (uintptr_t)end_pv - (uintptr_t)start_pv, |
| event_callback_arg, |
| event_stream_arg, |
| printf_callback_arg, |
| printf_stream_arg, |
| options, false); |
| } |