| /* | |
| * Copyright (c) 2008, 2024, 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; | |
| } | |
| }; | |
| JNIEXPORT | |
| 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 */ | |
| JNIEXPORT | |
| 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); | |
| } |