From cdc71f27c6ae9af644773be24e3bfd7488bd8dbd Mon Sep 17 00:00:00 2001 From: Christian Vogelgsang Date: Wed, 3 Aug 2016 22:10:10 +0200 Subject: [PATCH] added libtrace, a powerful library call tracer --- Makefile.am | 4 + doc/libtrace.md | 285 ++++++++ src/debug.cpp | 97 ++- src/devices.cpp | 3 + src/filesys.cpp | 24 +- src/include/newcpu.h | 1 + src/include/uae/libinfo.h | 26 + src/include/uae/libtrace.h | 12 + src/include/uae/patch.h | 3 + src/libinfo.cpp | 341 ++++++++++ src/libtrace.cpp | 1259 ++++++++++++++++++++++++++++++++++++ src/newcpu.cpp | 42 +- src/patch.cpp | 4 +- 13 files changed, 2061 insertions(+), 40 deletions(-) create mode 100644 doc/libtrace.md create mode 100644 src/include/uae/libinfo.h create mode 100644 src/include/uae/libtrace.h create mode 100644 src/libinfo.cpp create mode 100644 src/libtrace.cpp diff --git a/Makefile.am b/Makefile.am index 380f3c3f3..51932c98e 100644 --- a/Makefile.am +++ b/Makefile.am @@ -424,6 +424,8 @@ libuae_a_SOURCES = \ src/include/uae/inline.h \ src/include/uae/io.h \ src/include/uae/jitconfig.h \ + src/include/uae/libinfo.h \ + src/include/uae/libtrace.h \ src/include/uae/likely.h \ src/include/uae/limits.h \ src/include/uae/log.h \ @@ -463,6 +465,8 @@ libuae_a_SOURCES = \ src/jit/compemu_support.cpp \ src/jit/flags_x86.h \ src/keybuf.cpp \ + src/libinfo.cpp \ + src/libtrace.cpp \ src/logging.cpp \ src/main.cpp \ src/mame/a2410.cpp \ diff --git a/doc/libtrace.md b/doc/libtrace.md new file mode 100644 index 000000000..ce3ae1f5f --- /dev/null +++ b/doc/libtrace.md @@ -0,0 +1,285 @@ +# libtrace - A library call tracer for FS-UAE + +written by Christian Vogelgsang +under the GNU Public License V2 + +## Introduction + +**libtrace** is a debug and analysis feature for FS-UAE that allows you to +print out messages whenever a library call was executed in AmigaOS. If you +supply a .fd file for a library the trace message includes the function name, +all input parameters and also the return value. + +You define a trace profile by writing a config file that gets loaded when +you enable libtrace. In the config file you specify the libraries to be traced, +the associated .fd file and a set of functions that shall be traced. + +libtrace is controlled via the internal FS-UAE debugger and allows you to +load config files, enable/disable the feature, limit traces to a single task, +or write traces to a file. + +## Installation + +Currently, the libtrace feature is located in an own branch of my FS-UAE git +repository. + +Clone the [libtrace branch][1] and build FS-UAE from source. + +[1]: https://github.com/cnvogelg/fs-uae/tree/libtrace + +## Configuration File + +First you need to define the trace profile in a config file. Its typically +called **libtrace.cfg** and if it resides in the current directory when +launching FS-UAE it will be loaded and activated automatically. + +The config file is a text file with a list of one or multiple libraries, the +associated `.fd` files (if any) and the set of functions to instrument for +tracing: + +``` +name1.library +name1_lib.fd +* +# +name2.library +name2_lib.fd +198 +-30 +AllocMem +# +``` + +First line is the library name as it used on the Amiga for OpenLibrary(). + +Next line gives a host local file system path where to find the associated +.fd file for this library. Only if this file is available you will see the +function names and the arguments. If you don't have a .fd file for this +library then leave the line blank but do not omit it! + +Then a list of function names is given that is terminated with a hash **#** +line. Use asterisk * to trace all functions of a library. + +You can either state the function name, the LVO (library vector offset) as +a positive or negative number for each entry. + +Additionally, you can prefix each function entry with flag characters: + +* **!** do not trace the given function. Useful if you first include all + functions with * and then remove some functions again. +* **?** do not trace the result value. It speeds up tracing a bit and omits + the second return line durin trace. +* **<** enter debugger of FS-UAE right before the function gets called +* **>** enter debugger of FS-UAE right after the function has been called +* **+** show extra information. right now WaitPkt() shows the DOS packet + +## Example Config + +``` +dos.library +fd/dos_lib.fd ++* +!Open +# +exec.library +fd/exec_lib.fd +InitResident +# +``` + +This config traces both **dos.library** and **exec.library** and picks the +.fd files from a **fd** directory in the current directory. + +While in dos library we trace all functions (except Open()) with extra flag +(+), in exec library only the InitResident() call is traced. + +## Control libtrace + +The library tracer is controlled from the FS-UAE debugger. +Make sure to enable it in the FS-UAE configuration with: + +``` +console_debugger = 1 +``` + +and enter it with `F11 + d`. Now you can enter commands on the console. + +The following commands are available for libtrace: + +``` + L print libtrace status. + Le [<0-1] enable/disable/toggle libtrace + Lc 'config' set libtrace config file + Lt 'task' trace only task with this name + Lo 'output' write traces to output file +``` + +First set the config file with `Lc` command and enable the tracer with `Le 1`: + +``` +Lc 'mytrace.cfg' +Le 1 +``` + +Now you need to exit the monitor (`x` command) and reset the virtual Amiga +(with `F11 + r`) to activate the trace. + +By default library calls from all tasks running in AmigaOS are traced, but with +the `Lt` command you can limit the traces to a single task given by its name. + +The traces are written to the console by default. If you use the `Lo` command +you can redirect the trace output to a file. Use `Lo ''` to reset output to +console again. + +``` +Lt 'CON' +Lo 'console.log' +``` + +## Libtrace Output + +A function trace typically consists of two lines: an output before the call +is issued with the incoming parameters and a second line with the result of +the call (value of `d0`). + +The output looks like: + +``` +@00fcc816 0000f8c8:Initial CLI dos FindSegment(name[d1]=00001e45, seg[d2]=00000000, system[d3]=00000000) +@00fcc816 0000f8c8:Initial CLI dos FindSegment -> d0=00000000 +``` + +The first field on each line denotes the **current program counter (PC)** +location that entered the call. + +The next field is a pair of pointer and name and refers to the **exec task** +that is currently running. + +Then the **short name of the library** is given. + +Finally the **function name** with the arguments. Each argument is given +by name, register and passed value. + +The result line contains the value of `d0` after executing the call. + +## Limitations + +Currently, libtrace can only patch library entries that are coded as a `JMP` +jump to function. Special functions like cache clear or ancient BCPL +dos.library entries cannot be patched. + +However, it handles external patching of libraries with `SetFunction() and +can trace all libraries including expansion, exec, and dos. + +## Technical Details + +The basic idea for library tracing here is to replace the `JMP` opcode in the +library jump tables with special **a-line** opcodes `$axxx` that allow you to +execute custom native code in FS-UAE when the opcode is triggered during m68k +CPU emulation. By default the CPU emulation allows to execute code on a-line +opcodes only inside the FS-UAE extension area, but for libtrace we have to +enable this feature for all memory regions. + +Libtrace is initialized like many other FS-UAE extensions from a Resident +located in the virtual expansion area of FS-UAE. It is executed from expansion +library during system init. In this situation we first scan the existing +library list of the system if already libs are here that need to be +instrumented. Then we patch `(Old)OpenLibrary()` to track first opens of +future libs we want to instrument and also patch `SetFunction() to track +changes on instrumented libraries. + +A library is instrumented as follows: First we allocate a new (simple) a-line +trap for this library. Then we internally assign the library name and base +address with this new trap. Now we replace the `JMP` instruction of each +function entry we want to patch with the **a-line opcode**. Finally, the +library checksum is re-calculated. The patched `SetFunction() call ensures +that if a function entry is changed it still has the correct a-line opcode +instead. + +The approach is very compatible and also almost invisible to the running +system. + +A library call is now performed as follows: The user code executes a `JSR` to +the location of the function in the function table of the library. Instead +of directly executing the function with `JMP address`, an instrumented call +hits the a-line and enters our **pre_call_trace** function in FS-UAE. + +There the a-line opcode is retrieved from the current `PC`. With the a-line +opcode we can look up the library that is associated with it and already know +the library name and the library base address. Next we determine the **LVO +(library vector offset)** of the called function by subtracting the current +`PC` from the library base address. With the LVO we know what function was +called and can lookup function information from the .fd file. + +The .fd file provides the function name, the name of the arguments and the +associated CPU registers. The values are retrieved from the emulated CPU +and the pre call line of the trace is generated and written to the console. + +If we also want to trace the return value of the function then all information +on the current call including library and function details are filled into +a native malloc()ed structure and the pointer is pushed on the **Amiga** +stack(!). + +Then the address of a pre-allocated location in Amiga memory is pushed on the +stack. It contains a reserved **a-line opcode** that triggers our native +**post_call_trace** function after calling the library function. + +Finally we push the original function pointer found next to our a-line opcode +in the library on the stack. The trap is flagged as AUTO_RTS and that will +automatically execute and implicit `RTS` opcode when we leave our +``pre_call_trace`` function. + +``` +Stack without return value tracing: + * user code return address (pushed by initial JSR to lib) + * library function address (triggered by AUTO_RTS of lib a-line) + +Stack with return value tracing: + * user code return address (pushed by initial JSR to lib) + * native pointer (32 or 64 bit) to call info structure + * pointer to a-line opcode that call post_call_trace() + * library function address (triggered by AUTO_RTS of lib a-line) +``` + +If we are not interested in the return value then only the function address +is pushed on the stack. + +After leaving the pre call function, the AUTO_RTS will pop the function +address from the stack and finally execute the actual library function. + +Without return value tracing the final `RTS` in the function code will +retrieve the user code return address and return to user land. + +With enabled return value tracing the final `RTS` in the function code +pops the address of our post_call_trace() a-line opcode from the stack +and executes our native post_call_trcace() in FS-UAE. + +The post function pops the native pointer for the call info structure from +the Amiga stack and now has all information about this call available. It +finally reads out the result value from CPU register `d0` and outputs the +post trace line to the console. The call info is freed. + +After the post_call_trace() a-line an `RTS` is placed that retrieves the +user code return address from the stack and returns to user land. + +Note: we always have a slightly larger stack foot print compared to a function +that was not instrumented. Usually you can neglect this. But you can reduce +the stack consumption by omitting the return value trace. + +Note: exec's `StackSwap()` fiddles with the stack and therefore we enforce +that this call never traces the return value. While it is possible to place +our stack payload on the new stack, existing Amiga code places the new stack +right below the current stack and if we push extra values on the old stack to +pass on our native pointer we corrupt the new stack... + +EOF + + + + + + + + + + diff --git a/src/debug.cpp b/src/debug.cpp index 67ce3be57..3e40f5fb2 100644 --- a/src/debug.cpp +++ b/src/debug.cpp @@ -38,6 +38,7 @@ #include "calc.h" #include "uae/debuginfo.h" #include "uae/segtracker.h" +#include "uae/libtrace.h" #include "cpummu.h" #include "cpummu030.h" #include "ar.h" @@ -136,7 +137,7 @@ static TCHAR help[] = { _T(" fd Remove all breakpoints.\n") _T(" fs | Wait n scanlines/position.\n") _T(" fc Wait n color clocks.\n") - _T(" fS Break when (SR & mask) = val.\n") + _T(" fS Break when (SR & mask) = val.\n") _T(" f Step forward until <= PC <= .\n") _T(" e Dump contents of all custom registers, ea = AGA colors.\n") _T(" i [] Dump contents of interrupt and trap vectors.\n") @@ -182,6 +183,11 @@ static TCHAR help[] = { _T(" Zy 'symbol' find symbol address.\n") _T(" Zc 'file' find source code line address.\n") #endif /* WITH_SEGTRACKER */ + _T(" L print libtrace status.\n") + _T(" Le [<0-1] enable/disable/toggle libtrace\n") + _T(" Lc 'config' set libtrace config file\n") + _T(" Lt 'task' trace only task with this name\n") + _T(" Lo 'output' write traces to output file\n") _T(" ? Hex ($ and 0x)/Bin (%)/Dec (!) converter.\n") #ifdef _WIN32 _T(" x Close debugger.\n") @@ -2928,7 +2934,7 @@ static void writeintomem (TCHAR **c) if (!more_params (c)) break; val = readhex (c, &len); - + if (len == 4) { put_long (addr, val); cc = 'L'; @@ -3529,7 +3535,7 @@ static void show_exec_lists (TCHAR *t) console_out_f (_T("%08x %d %d %s\n"), node, (int)((v >> 8) & 0xff), (uae_s8)(v & 0xff), name); xfree (name); console_out_f (_T("Attributes %04x First %08x Lower %08x Upper %08x Free %d\n"), - get_word_debug (node + 14), get_long_debug (node + 16), get_long_debug (node + 20), + get_word_debug (node + 14), get_long_debug (node + 16), get_long_debug (node + 20), get_long_debug (node + 24), get_long_debug (node + 28)); uaecptr mc = get_long_debug (node + 16); while (mc) { @@ -4104,7 +4110,7 @@ static void debug_sprite (TCHAR **inptr) int debug_write_memory_16 (uaecptr addr, uae_u16 v) { addrbank *ad; - + ad = &get_mem_bank (addr); if (ad) { ad->wput (addr, v); @@ -4115,7 +4121,7 @@ int debug_write_memory_16 (uaecptr addr, uae_u16 v) int debug_write_memory_8 (uaecptr addr, uae_u8 v) { addrbank *ad; - + ad = &get_mem_bank (addr); if (ad) { ad->bput (addr, v); @@ -4126,7 +4132,7 @@ int debug_write_memory_8 (uaecptr addr, uae_u8 v) int debug_peek_memory_16 (uaecptr addr) { addrbank *ad; - + ad = &get_mem_bank (addr); if (ad->flags & (ABFLAG_RAM | ABFLAG_ROM | ABFLAG_ROMIN | ABFLAG_SAFE)) return ad->wget (addr); @@ -4139,7 +4145,7 @@ int debug_peek_memory_16 (uaecptr addr) int debug_read_memory_16 (uaecptr addr) { addrbank *ad; - + ad = &get_mem_bank (addr); if (ad) return ad->wget (addr); @@ -4148,7 +4154,7 @@ int debug_read_memory_16 (uaecptr addr) int debug_read_memory_8 (uaecptr addr) { addrbank *ad; - + ad = &get_mem_bank (addr); if (ad) return ad->bget (addr); @@ -4297,6 +4303,8 @@ static int parse_string(TCHAR **inptr, TCHAR *str, int max_len) if (**inptr != 0) { (*inptr)++; } + } else { + return -1; } str[len] = '\0'; return len; @@ -4460,6 +4468,76 @@ static void segtracker(TCHAR **inptr) #endif /* WITH_SEGTRACKER */ +static void libtrace(TCHAR **inptr) +{ + int show_status = 0; + if (more_params (inptr)) { + switch (next_char (inptr)) + { + case 'e': /* 'Le' enable/disable/toggle libtrace */ + { + int enable = libtrace_is_enabled(); + ignore_ws(inptr); + if(more_params(inptr)) { + int v = readint(inptr); + enable = v ? 1 : 0; + } else { + enable = enable ? 0 : 1; + } + show_status = 1; + if(enable != libtrace_is_enabled()) { + libtrace_enable(enable); + if(enable) { + console_out_f(_T("Reset Amiga to activate libtrace!\n")); + } + } + } + break; + case 'c': /* 'Lc' set config file */ + { + TCHAR str[256]; + int len = parse_string(inptr, str, 256); + if(len > 0) { + libtrace_set_cfg_file(str); + } + console_out_f(_T("libtrace config file: '%s'\n"), libtrace_get_cfg_file()); + } + break; + case 't': /* 'Lt' set task to trace */ + { + TCHAR str[256]; + int len = parse_string(inptr, str, 256); + if(len > 0) { + libtrace_set_task_name(str); + } else if(len == 0) { + libtrace_set_task_name(NULL); + } + console_out_f(_T("libtrace task: %s\n"), libtrace_get_task_name()); + } + break; + case 'o': /* 'Lo' set set output file */ + { + TCHAR str[256]; + int len = parse_string(inptr, str, 256); + if(len > 0) { + libtrace_set_output_file(str); + } else if(len == 0) { + libtrace_set_output_file(NULL); + } + console_out_f(_T("libtrace output file: %s\n"), libtrace_get_output_file()); + } + break; + } + } else { + /* only 'L': show libtrace status */ + show_status = 1; + } + + if(show_status) { + console_out_f(_T("libtrace is %s\n"), libtrace_is_enabled() ? "enabled":"disabled"); + } +} + #ifdef WITH_PPC static void ppc_disasm(uaecptr addr, uaecptr *nextpc, int cnt) { @@ -4858,6 +4936,9 @@ static bool debug_line (TCHAR *input) segtracker(&inptr); break; #endif /* WITH_SEGTRACKER */ + case 'L': + libtrace(&inptr); + break; case 'h': case '?': if (more_params (&inptr)) diff --git a/src/devices.cpp b/src/devices.cpp index 35f7cf8da..bf115f88f 100644 --- a/src/devices.cpp +++ b/src/devices.cpp @@ -60,6 +60,7 @@ #include "ethernet.h" #include "uae/debuginfo.h" #include "uae/segtracker.h" +#include "uae/libtrace.h" #ifdef RETROPLATFORM #include "rp.h" #endif @@ -299,6 +300,7 @@ void do_leave_program (void) DISK_free (); close_sound (); dump_counts (); + libtrace_cleanup(); #ifdef PARALLEL_PORT parallel_exit(); #endif @@ -381,6 +383,7 @@ void virtualdevice_init (void) #ifdef WITH_SEGTRACKER segtracker_install (); #endif /* WITH_SEGTRACKER */ + libtrace_install (); uaeres_install (); hardfile_install (); #endif diff --git a/src/filesys.cpp b/src/filesys.cpp index 96701cd8c..c064a7378 100644 --- a/src/filesys.cpp +++ b/src/filesys.cpp @@ -54,6 +54,7 @@ #include "uaeresource.h" #include "uae/debuginfo.h" #include "uae/segtracker.h" +#include "uae/libtrace.h" #include "inputdevice.h" #include "clipboard.h" #include "consolehook.h" @@ -1088,7 +1089,7 @@ static void initialize_mountinfo (void) if (added) allocuci (&currprefs, nr, -1); } - + } @@ -2002,7 +2003,7 @@ static uae_u32 filesys_media_change_reply (TrapContext *ctx, int mode) put_byte (u->volume + 44, 0); put_byte (u->volume + 172 - 32, 1); } - + xfree (u->mount_volume); xfree (u->mount_rootdir); u->mount_rootdir = NULL; @@ -2010,7 +2011,7 @@ static uae_u32 filesys_media_change_reply (TrapContext *ctx, int mode) } else { u->mount_changed = 0; } - + return 1; } @@ -2029,9 +2030,9 @@ int filesys_media_change (const TCHAR *rootdir, int inserted, struct uaedev_conf return 0; if (automountunit >= 0) return -1; - + write_log (_T("filesys_media_change('%s',%d,%p)\n"), rootdir, inserted, uci); - + nr = -1; for (u = units; u; u = u->next) { if (is_virtual (u->unit)) { @@ -2089,7 +2090,7 @@ int filesys_media_change (const TCHAR *rootdir, int inserted, struct uaedev_conf } if (inserted < 0) /* -1 = only mount if already exists */ return 0; - /* new volume inserted and it was not previously mounted? + /* new volume inserted and it was not previously mounted? * perhaps we have some empty device slots? */ nr = filesys_insert (-1, volptr, rootdir, 0, 0); if (nr >= 100) { @@ -3819,7 +3820,7 @@ static void action_read_link (Unit *unit, dpacket packet) _tcscat (tmp, extrapath); } xfree (extrapath); - write_log (_T("got target '%s'\n"), tmp); + write_log (_T("got target '%s'\n"), tmp); char *s = ua_fs (tmp, -1); for (i = 0; s[i]; i++) { if (i >= size - 1) @@ -5222,7 +5223,7 @@ static void } else if (!valid_address (addr, size)) { /* it really crosses memory boundary */ uae_u8 *buf; - + /* ugh this is inefficient but easy */ if (key_seek(k, k->file_pos, SEEK_SET) < 0) { @@ -7162,6 +7163,7 @@ static uae_u32 REGPARAM2 filesys_diagentry (TrapContext *context) #ifdef WITH_SEGTRACKER resaddr = segtracker_startup(resaddr); #endif + resaddr = libtrace_startup(resaddr); resaddr = uaeres_startup (resaddr); #ifdef BSDSOCKET resaddr = bsdlib_startup (resaddr); @@ -7296,7 +7298,7 @@ static uae_u32 REGPARAM2 filesys_diagentry (TrapContext *context) } m68k_areg (regs, 0) = residents; - + if (currprefs.uae_hide_autoconfig && expansion) { bool found = true; while (found) { @@ -7338,7 +7340,7 @@ static uae_u32 REGPARAM2 filesys_dev_bootfilesys (TrapContext *context) UnitInfo *uip = &mountinfo.ui[unit_no]; int iscd = (m68k_dreg (regs, 6) & 0x80000000) != 0 || uip->unit_type == UNIT_CDFS; int type; - + if (iscd) { if (!get_long (devicenode + 16)) put_long (devicenode + 16, cdfs_handlername); @@ -8197,7 +8199,7 @@ static void get_new_device (int type, uaecptr parmpacket, TCHAR **devname, uaecp (uae_u32)(mountinfo.ui[unit_no].hf.virtsize), mountinfo.ui[unit_no].rootdir); } - + /* Fill in per-unit fields of a parampacket */ static uae_u32 REGPARAM2 filesys_dev_storeinfo (TrapContext *context) { diff --git a/src/include/newcpu.h b/src/include/newcpu.h index 285392ffa..01acc8766 100644 --- a/src/include/newcpu.h +++ b/src/include/newcpu.h @@ -275,6 +275,7 @@ STATIC_INLINE uae_u32 munge24 (uae_u32 x) return x & regs.address_space_mask; } +extern int traps_everywhere; extern int mmu_enabled, mmu_triggered; extern int cpu_cycles; extern int cpucycleunit; diff --git a/src/include/uae/libinfo.h b/src/include/uae/libinfo.h new file mode 100644 index 000000000..9576b0a93 --- /dev/null +++ b/src/include/uae/libinfo.h @@ -0,0 +1,26 @@ +typedef struct { + char *name; + int reg; +} arg_info_t; + +typedef struct func_info { + struct func_info *next; + char *name; + int lvo; + int num_args; + arg_info_t *args; +} func_info_t; + +typedef struct { + char *name; + int num_funcs; + int max_lvo; + func_info_t *funcs; +} lib_info_t; + +typedef const char ** str_p; + +extern lib_info_t *libinfo_read_fd(const char *fd_file_name, str_p err_str); +extern void libinfo_dump(lib_info_t *li); +extern void libinfo_free(lib_info_t *li); +extern func_info_t *libinfo_find_func(lib_info_t *li, const char *name); diff --git a/src/include/uae/libtrace.h b/src/include/uae/libtrace.h new file mode 100644 index 000000000..0f5e86d3f --- /dev/null +++ b/src/include/uae/libtrace.h @@ -0,0 +1,12 @@ +extern uaecptr libtrace_startup(uaecptr resaddr); +extern void libtrace_install(void); +extern void libtrace_cleanup(void); + +extern void libtrace_set_cfg_file(const char *cfg_file); +extern const char *libtrace_get_cfg_file(void); +extern void libtrace_enable(int enable); +extern int libtrace_is_enabled(void); +extern void libtrace_set_task_name(const char *task_name); +extern const char *libtrace_get_task_name(void); +extern void libtrace_set_output_file(const char *out_file); +extern const char *libtrace_get_output_file(void); diff --git a/src/include/uae/patch.h b/src/include/uae/patch.h index 82fe5d7f8..722439ce7 100644 --- a/src/include/uae/patch.h +++ b/src/include/uae/patch.h @@ -17,3 +17,6 @@ extern void patch_func_init_post_call(TrapHandler func, patch_func *pf, int flags, int save); extern void patch_func_set(TrapContext *ctx, uaecptr lib_base, uae_s16 func_lvo, patch_func *pf); + +extern uae_u32 patch_SetFunction(TrapContext *ctx, uae_u32 lib_base, + uae_s16 offset, uae_u32 new_func); diff --git a/src/libinfo.cpp b/src/libinfo.cpp new file mode 100644 index 000000000..9e331197e --- /dev/null +++ b/src/libinfo.cpp @@ -0,0 +1,341 @@ +/* +* UAE - The Un*x Amiga Emulator +* +* Library Info from FD Files +* +* Written by Christian Vogelgsang +*/ + +#include +#include +#include +#include + +#include "uae/libinfo.h" + +typedef struct { + int lvo; + int pub; + int line; + lib_info_t *li; + func_info_t *cur_func; + str_p err_str; +} state_t; + +static int parse_cmd(char *line, state_t *s) +{ + /* check for arg */ + char *arg = strchr(line, ' '); + if(arg != NULL) { + *arg = '\0'; + arg++; + } + + if(strcmp(line, "base")==0) { + /* ignore base name */ + } + else if(strcmp(line, "bias")==0) { + s->lvo = atoi(arg); + } + else if(strcmp(line, "public")==0) { + s->pub = 1; + } + else if(strcmp(line, "private")==0) { + s->pub = 0; + } + else if(strcmp(line, "end")==0) { + /* ignore */ + } + else { + *s->err_str = "unknown command"; + return 1; + } + return 0; +} + +#define MAX_ARG 10 + +static func_info_t *parse_func(char *line, state_t *s) +{ + char *arg_names[MAX_ARG]; + char *arg_regs[MAX_ARG]; + int num_arg_names; + int num_arg_regs; + + /* scan name */ + char *func_name = line; + while(*line != '\0') { + if(*line == '(') { + *line = '\0'; + line++; + break; + } + else if(!isalnum(*line)) { + *s->err_str = "func name?"; + return NULL; + } + line++; + } + + /* scan arg names */ + int begin = 1; + num_arg_names = 0; + while(*line != '\0') { + if(*line == ',') { + begin = 1; + *line = '\0'; + line++; + } + else if(*line == ')') { + *line = '\0'; + line++; + break; + } + else if(isalnum(*line) || *line == '_') { + if(begin) { + if(num_arg_names == MAX_ARG) { + *s->err_str = "too many arg names"; + return NULL; + } + arg_names[num_arg_names] = line; + num_arg_names++; + begin = 0; + } + line++; + } + else { + *s->err_str = "arg name?"; + return NULL; + } + } + + /* expect start of regs */ + if(*line != '(') { + *s->err_str = "no arg reg begin?"; + return NULL; + } + line++; + + /* scan arg regs */ + begin = 1; + num_arg_regs = 0; + while(*line != '\0') { + if(*line == ',' || *line == '/') { + begin = 1; + *line = '\0'; + line++; + } + else if(*line == ')') { + *line = '\0'; + line++; + break; + } + else if(isalnum(*line)) { + if(begin) { + if(num_arg_names == MAX_ARG) { + *s->err_str = "too many arg regs"; + return NULL; + } + arg_regs[num_arg_regs] = line; + num_arg_regs++; + begin = 0; + } + line++; + } + else { + *s->err_str = "arg reg?"; + return NULL; + } + } + + /* arg names regs size msimatch? */ + if(num_arg_regs != num_arg_names) { + *s->err_str = "arg names vs regs?"; + return NULL; + } + + /* fill func info */ + func_info_t *fi = (func_info_t *)malloc(sizeof(func_info_t)); + if(fi == NULL) { + *s->err_str = "no mem"; + return NULL; + } + fi->next = NULL; + fi->name = strdup(func_name); + fi->lvo = s->lvo; + fi->num_args = num_arg_regs; + arg_info_t *args = (arg_info_t *)malloc(sizeof(arg_info_t) * num_arg_regs); + fi->args = args; + if(args == NULL) { + *s->err_str = "no mem"; + free(fi); + return NULL; + } + + /* store args */ + for(int i=0;ipub) { + func_info_t * fi = parse_func(line, s); + if(fi != NULL) { + /* store first func info in lib info */ + if(s->cur_func == NULL) { + s->li->funcs = fi; + } + else { + s->cur_func->next = fi; + } + s->cur_func = fi; + s->li->num_funcs++; + } else { + return 1; + } + } + /* update state */ + s->lvo += 6; + return 0; + } + + /* unknown line */ + else { + *s->err_str = "unknown line"; + return 1; + } +} + +lib_info_t *libinfo_read_fd(const char *fd_file_name, str_p err_str) +{ + lib_info_t *li = (lib_info_t *)malloc(sizeof(lib_info_t)); + if(li == NULL) { + return NULL; + } + li->num_funcs = 0; + li->max_lvo = 0; + li->funcs = NULL; + li->name = strdup(fd_file_name); + + /* read fd file */ + FILE *fh = fopen(fd_file_name, "rb"); + if(fh == NULL) { + *err_str = "error opening file"; + free(li->name); + free(li); + return NULL; + } + + /* init state */ + state_t state = { 0, 0, 0 }; + state.li = li; + state.cur_func = NULL; + state.err_str = err_str; + + /* read file line by line */ + while(!feof(fh)) { + char line[80]; + if(fgets(line, 80, fh) == NULL) { + break; + } + + /* remove lf */ + char *ptr = strchr(line, '\n'); + if(ptr != NULL) { + *ptr = '\0'; + } + + if(parse_fd_line(line, &state) != 0) { + libinfo_free(li); + fclose(fh); + return NULL; + } + } + + if(state.cur_func != NULL) { + li->max_lvo = state.cur_func->lvo; + } + + fclose(fh); + return li; +} + +void libinfo_dump(lib_info_t *li) +{ + printf("libinfo for '%s'\n", li->name); + printf("num_funcs=%d max_lvo=%d\n", li->num_funcs, li->max_lvo); + func_info_t *fi = li->funcs; + while(fi != NULL) { + printf("func %s, lvo=%d, num_args=%d\n", fi->name, fi->lvo, fi->num_args); + arg_info_t *ai = fi->args; + for(int i=0;inum_args;i++) { + printf(" arg %s, reg=%d\n", ai[i].name, ai[i].reg); + } + fi = fi->next; + } +} + +void libinfo_free(lib_info_t *li) +{ + if(li == NULL) { + return; + } + + func_info_t *fi = li->funcs; + while(fi != NULL) { + func_info_t *next = fi->next; + arg_info_t *ai = fi->args; + for(int i=0;inum_args;i++) { + free(ai[i].name); + } + free(fi->args); + free(fi->name); + free(fi); + fi = next; + } + + free(li->name); + free(li); +} + +func_info_t *libinfo_find_func(lib_info_t *li, const char *name) +{ + func_info_t *fi = li->funcs; + while(fi != NULL) { + if(strcmp(fi->name, name)==0) { + return fi; + } + fi = fi->next; + } + return NULL; +} diff --git a/src/libtrace.cpp b/src/libtrace.cpp new file mode 100644 index 000000000..0e1823292 --- /dev/null +++ b/src/libtrace.cpp @@ -0,0 +1,1259 @@ +/* +* UAE - The Un*x Amiga Emulator +* +* Library Tracer +* +* Written by Christian Vogelgsang +*/ + +#include "sysconfig.h" +#include "sysdeps.h" +#include "options.h" + +#include "uae/memory.h" +#include "autoconf.h" +#include "traps.h" +#include "newcpu.h" +#include "uae/libtrace.h" +#include "uae/libinfo.h" +#include "uae/patch.h" +#include "debug.h" + +#define JSR 0x4eb9 +#define JMP 0x4ef9 + +#define MAX_LIBS 10 + +#define LIB_TAG_NONE 0 +#define LIB_TAG_EXEC 1 +#define LIB_TAG_DOS 2 + +#define FUNC_FLAG_VOID 1 +#define FUNC_FLAG_PRE_DEBUG 2 +#define FUNC_FLAG_POST_DEBUG 4 +#define FUNC_FLAG_INVERT 8 +#define FUNC_FLAG_ALL 16 +#define FUNC_FLAG_EXTRA 32 + +typedef struct { + char *name; + int reg; +} func_arg_t; + +typedef struct { + uae_u32 func_addr; + int flags; + func_info_t *info; +} func_entry_t; + +typedef struct lvo_entry { + struct lvo_entry *next; + const char *name; + int flags; +} lvo_entry_t; + +typedef struct lib_entry { + struct lib_entry *next; + /* config section */ + const char *lib_name; + const char *fd_name; + lvo_entry_t *lvo_names; + /* init */ + char *short_name; + lib_info_t *lib_info; + int tag; + uae_u16 trap_code; + /* runtime */ + uae_u32 lib_base; + uae_u32 neg_size; + int num_funcs; + func_entry_t *funcs; +} lib_entry_t; + +typedef struct { + lib_entry_t *lib; + func_entry_t *func; + uae_u32 lvo; + uae_u32 func_ptr; + uae_u32 this_task; + uae_u32 callee_pc; + uae_u32 regs[16]; + uae_u32 return_regs[2]; + const char *task_name; +} call_info_t; + +// globals +static int enable; +static int reload; +static FILE *out_fh; +static const char *cfg_file; +static const char *task_name; +static const char *out_file; +static uae_u32 exec_base; +static lib_entry_t *libs; +static uae_u16 lib_traps[MAX_LIBS]; + +// addresses for traps and old lib addresses +static uae_u32 init_addr; +static uae_u32 setfunc_addr; +static uae_u32 old_setfunc_addr; +static uae_u32 oldopenlib_addr; +static uae_u32 old_oldopenlib_addr; +static uae_u32 openlib_addr; +static uae_u32 old_openlib_addr; +static uae_u32 log_post_addr; + +static uae_u32 REGPARAM2 trap_init(TrapContext *ctx); +static uae_u32 REGPARAM2 trap_setfunc(TrapContext *ctx); +static uae_u32 REGPARAM2 trap_openlib(TrapContext *ctx); +static uae_u32 REGPARAM2 trap_trace_call_pre(TrapContext *ctx); +static uae_u32 REGPARAM2 trap_trace_call_post(TrapContext *ctx); + +static int patch_lib_func(lib_entry_t *lib, uae_u32 lvo) +{ + uae_u32 ptr = lib->lib_base - lvo; + + // make sure its a valid lvo + if(lvo % 6 != 0) { + printf("libtrace: patch failed: invalid lvo=%d modulo! '%s'\n", + lvo, lib->lib_name); + return 0; + } + if(lvo > lib->neg_size) { + printf("libtrace: patch failed: lvo=%d out of range! '%s'\n", + lvo, lib->lib_name); + return 0; + } + + // only patch lib entry that has a JMP in the table + if(get_word(ptr) != JMP) { + printf("libtrace: patch failed: No JMP in library table! '%s' lib_base=%08x, lvo=%d\n", + lib->lib_name, lib->lib_base, lvo); + return 0; + } + + /* place trap of library instead of JMP */ + put_word(ptr, lib->trap_code); + +#if 0 + printf("libtrace patch lvo: %d func: %08x: old_addr=%08x jmp=%04x\n", + lvo, ptr, get_long(ptr+2), get_word(ptr)); +#endif + + /* store address of lib pointer in func */ + func_entry_t *func = lib->funcs; + int idx = (lvo - 6) / 6; + func[idx].func_addr = ptr + 2; + + return 1; +} + +static void pre_patch(TrapContext *ctx) +{ + /* Forbid */ + CallLib(ctx, exec_base, -132); +} + +static void post_patch(TrapContext *ctx, uae_u32 lib_base) +{ + /* set LIBF_CHANGED(2) in lib_Flags */ + put_byte(lib_base + 14, get_byte(lib_base + 14) | 2); + + /* SumLibrary a1=lib */ + uae_u32 old_a1 = m68k_areg(regs, 1); + m68k_areg(regs, 1) = lib_base; + CallLib(ctx, exec_base, -426); + m68k_areg(regs, 1) = old_a1; + + /* Permit */ + CallLib(ctx, exec_base, -138); +} + +static uae_u32 get_lvo_for_name(lib_info_t *li, const char *lvo_name) +{ + /* check if its a name */ + if(li != NULL) { + func_info_t *fi = libinfo_find_func(li, lvo_name); + if(fi != NULL) { + return fi->lvo; + } + } + /* try numveric conversion */ + return atoi(lvo_name); +} + +static void mark_func(lib_entry_t *lib, uae_u32 lvo, int on, int flags) +{ + func_entry_t *func = lib->funcs; + int idx = (lvo - 6) / 6; + func[idx].func_addr = on ? 0xffffffff : 0; + func[idx].flags = flags; +} + +static int mark_patch_funcs(lib_entry_t *lib) +{ + /* is info available? */ + lib_info_t *li = lib->lib_info; + + /* get lvo entries from config */ + lvo_entry_t *lvo_ptr = lib->lvo_names; + if(lvo_ptr == NULL) + { + return 0; + } + + /* run through config */ + int num_patches = 0; + while(lvo_ptr != NULL) { + int flags = lvo_ptr->flags; + + /* patch all entries? */ + if(flags & FUNC_FLAG_ALL) { + /* if info is available then patch only known functions */ + if(li != NULL) { + func_info_t *finfo = li->funcs; + while(finfo!=NULL) { + mark_func(lib, finfo->lvo, 1, flags); + num_patches++; + finfo = finfo->next; + } + } + /* otherwise patch all */ + else { + uae_u32 lvo = 30; + while(lvo < lib->neg_size) { + mark_func(lib, lvo, 1, flags); + num_patches++; + lvo += 6; + } + } + } + /* normal entry with name and flags */ + else { + const char *lvo_name = lvo_ptr->name; + uae_u32 lvo = get_lvo_for_name(li, lvo_name); + /* patch? */ + if(lvo > 0) { + int on = (flags & FUNC_FLAG_INVERT) == 0; + mark_func(lib, lvo, on, flags); + num_patches++; + } + } + lvo_ptr = lvo_ptr->next; + } + return num_patches; +} + +static void patch_lib(TrapContext *ctx, lib_entry_t *lib) +{ + printf("libtrace: patch lib @%08x '%s' #%d\n", + lib->lib_base, lib->lib_name, lib->num_funcs); + + /* make functions to be patched */ + int num_patches = mark_patch_funcs(lib); + if(num_patches == 0) { + printf("libtrace: NO PATCHES FOUND!\n"); + return; + } + + pre_patch(ctx); + + /* perform patching */ + uae_u32 lvo = 6; + func_entry_t *func = lib->funcs; + for(int i=0; i < lib->num_funcs; i++) { + if(func->func_addr != 0) { + patch_lib_func(lib, lvo); + } + lvo += 6; + func++; + } + + /* if patching exec's StackSwap() function then it is crucial + to set the FUNC_FLAG_VOID to avoid the post trace call. + It won't work after changing the stack! */ + if(lib->tag == LIB_TAG_EXEC) { + // LVO=-732 -> offset 121 + if(lib->funcs[121].func_addr != 0) { + lib->funcs[121].flags |= FUNC_FLAG_VOID; + } + } + + post_patch(ctx, lib->lib_base); +} + +static void unpatch_lib(lib_entry_t *lib) +{ + func_entry_t *func = lib->funcs; + for(int i=0;inum_funcs;i++) { + if(func->func_addr != 0) { + uae_u32 addr = func->func_addr - 2; + put_word(addr, JMP); + func->func_addr = 0; + } + func++; + } +} + +static void activate_libtrace(TrapContext *ctx, lib_entry_t *lib, uae_u32 lib_base) +{ + // store base + lib->lib_base = lib_base; + + // number of functions + lib->neg_size = get_word(lib_base + 16); + lib->num_funcs = lib->neg_size / 6; // neg_size + + printf("libtrace: activate '%s': funcs=%d\n", lib->lib_name, lib->num_funcs); + + // allocate funcs + size_t num_bytes = sizeof(func_entry_t) * lib->num_funcs; + lib->funcs = (func_entry_t *)malloc(num_bytes); + memset(lib->funcs, 0, num_bytes); + + // attach func info to funcs (if available) + func_info_t *finfo = NULL; + if(lib->lib_info != NULL) { + finfo = lib->lib_info->funcs; + } + func_entry_t *f = lib->funcs; + int lvo = 6; + for(int i=0; i < lib->num_funcs; i++) { + // skip to next lvo + while((finfo != NULL) && (finfo->lvo < lvo)) { + finfo = finfo->next; + } + // if lvo has info then store it in func + if((finfo != NULL) && (finfo->lvo == lvo)) { + f->info = finfo; + } + f++; + lvo+=6; + } + + // finally patch lib + if(lib->funcs != NULL) { + patch_lib(ctx, lib); + } +} + +static void deactivate_libtrace(lib_entry_t *lib, int unpatch) +{ + if(lib->lib_base != 0) { + printf("libtrace: deactivate '%s'\n", lib->lib_name); + + // set base to 0 to free entry + lib->lib_base = 0; + lib->num_funcs = 0; + if(lib->funcs != NULL) { + // really unpatch and remove TRAPs from lib jump table? + if(unpatch) { + unpatch_lib(lib); + } + free(lib->funcs); + lib->funcs = NULL; + } + } +} + +static void deactivate_libtraces(int unpatch) +{ + lib_entry_t *lib = libs; + while(lib != NULL) { + deactivate_libtrace(lib, unpatch); + lib = lib->next; + } +} + +static lib_entry_t *find_lib_by_trap_code(uae_u16 trap_code) +{ + lib_entry_t *lib = libs; + while(lib != NULL) { + if(lib->trap_code == trap_code) { + return lib; + } + lib = lib->next; + } + return NULL; +} + +static lib_entry_t *find_lib_by_name(const char *name) +{ + lib_entry_t *lib = libs; + while(lib != NULL) { + if(strcmp(lib->lib_name, name)==0) { + return lib; + } + lib = lib->next; + } + return NULL; +} + +// ---------- CONFIG -------------------------------------------------------- + +static int read_line(char *buf, int size, FILE *fh) +{ + char *result = fgets(buf, size, fh); + if(result == NULL) { + return -1; + } + int n = strlen(buf); + // stip newline/return + if(n > 0) { + if(buf[n-1] == '\n') { + buf[n-1] = '\0'; + n--; + } + } + if(n > 0) { + if(buf[n-1] == '\r') { + buf[n-1] = '\0'; + n--; + } + } + return n; +} + +static lib_entry_t *read_config(const char *cfg_file) +{ + char line[80]; + + printf("libtrace: reading config '%s'\n", cfg_file); + FILE *fh = fopen(cfg_file, "r"); + if(fh == NULL) { + return NULL; + } + + // read libs + lib_entry_t *lib = NULL; + int num_libs = 0; + while(1) { + // read lib name + int n = read_line(line, 79, fh); + if(n<0) { + break; + } + // end of config + if((n==0) || (line[0]=='#')) { + break; + } + + // limit number of libs + if(num_libs >= MAX_LIBS) { + printf("libtrace:cfg: too many libs!\n"); + break; + } + num_libs++; + + // alloc new lib + lib_entry_t *new_lib = (lib_entry_t *)malloc(sizeof(lib_entry_t)); + if(new_lib == NULL) { + printf("libtrace:cfg: no mem!\n"); + break; + } + memset(new_lib, 0, sizeof(lib_entry_t)); + + new_lib->lib_name = strdup(line); + new_lib->next = lib; + + // read fd name + n = read_line(line, 69, fh); + if(n<0) { + printf("libtrace:cfg: no fd name!\n"); + break; + } + if(n<2) { + new_lib->fd_name = NULL; + } else { + new_lib->fd_name = strdup(line); + } + + // read lvos + new_lib->lvo_names = NULL; + while(1) { + int n = read_line(line, 79, fh); + if(n<0) { + printf("libtrace:cfg: no lvo name!\n"); + break; + } + // parse lvo flags + char *ptr = line; + int flags = 0; + while(n>0) { + char c = ptr[0]; + if(c == '!') { + flags |= FUNC_FLAG_INVERT; + } + else if(c == '<') { + flags |= FUNC_FLAG_PRE_DEBUG; + } + else if(c == '>') { + flags |= FUNC_FLAG_POST_DEBUG; + } + else if(c == '?') { + flags |= FUNC_FLAG_VOID; + } + else if(c == '+') { + flags |= FUNC_FLAG_EXTRA; + } + else if(c == '-') { + /* simply ignore negative offset */ + } + else { + /* unknown flag -> stop */ + break; + } + ptr++; + n--; + } + char *name = NULL; + if(n==1) { + // all lvos? + if (*ptr == '*') { + flags |= FUNC_FLAG_ALL; + } + // end of lvo? + else if (*ptr == '#') { + break; + } + else { + name = strdup(ptr); + } + } + else if(n==0) { + break; + } + else { + name = strdup(ptr); + } + // alloc lvo + lvo_entry_t *lvo = (lvo_entry_t *)malloc(sizeof(lvo_entry_t)); + if(lvo == NULL) { + printf("libtrace:cfg: no lvo mem!\n"); + break; + } + lvo->name = name; + lvo->flags = flags; + lvo->next = new_lib->lvo_names; + new_lib->lvo_names = lvo; + } + lib = new_lib; + } + + fclose(fh); + return lib; +} + +static void setup_lib(lib_entry_t *lib, int offset) +{ + printf("libtrace: setup '%s'\n", lib->lib_name); + + // set trap code for this library + lib->trap_code = lib_traps[offset]; + + // read fd file? + if(lib->fd_name != NULL) { + printf("libtrace: load fd from '%s': ", lib->fd_name); + const char *err_str = NULL; + lib->lib_info = libinfo_read_fd(lib->fd_name, &err_str); + if(lib->lib_info == NULL) { + printf("FAILED: %s\n", err_str); + } else { + printf("ok.\n"); + } + } + + // set a lib tag if the lib is known (for quicker access) + if(strcmp(lib->lib_name,"exec.library")==0) { + lib->tag = LIB_TAG_EXEC; + } + else if(strcmp(lib->lib_name,"dos.library")==0) { + lib->tag = LIB_TAG_DOS; + } + else { + lib->tag = LIB_TAG_NONE; + } + + // get short name + const char *ptr = strchr(lib->lib_name, '.'); + if(ptr != NULL) { + int short_len = ptr - lib->lib_name; + char *name = (char *)malloc(short_len+1); + strncpy(name, lib->lib_name, short_len); + name[short_len] = '\0'; + lib->short_name = name; + } else { + lib->short_name = strdup(lib->lib_name); + } +} + +static void setup_libs(lib_entry_t *lib) +{ + int offset = 0; + while(lib != NULL) { + setup_lib(lib, offset); + offset++; + lib = lib->next; + } +} + +static void free_lib(lib_entry_t *lib) +{ + printf("libtrace: free '%s'\n", lib->lib_name); + + // free fd file? + if(lib->lib_info != NULL) { + libinfo_free(lib->lib_info); + lib->lib_info = NULL; + } + + // free lvo config + lvo_entry_t *lvo = lib->lvo_names; + while(lvo != NULL) { + lvo_entry_t *next = lvo->next; + free(lvo); + lvo = next; + } + + // free dynmic part + deactivate_libtrace(lib, 0); + + // free static part + free(lib->short_name); + free((void *)lib->lib_name); + free((void *)lib->fd_name); + + // finally free lib itself + free(lib); +} + +static void free_libs(void) +{ + // free libs + lib_entry_t *lib = libs; + while(lib != NULL) { + lib_entry_t *next_lib = lib->next; + free_lib(lib); + lib = next_lib; + } + libs = NULL; +} + +// ---------- STARTUP ------------------------------------------------------- + +/* first stage init: once at program startup */ +void libtrace_install (void) +{ + printf("libtrace: install\n"); + + // setup initial config + cfg_file = strdup("libtrace.cfg"); + reload = 1; + + // setup init trap to setup patches + init_addr = here (); + calltrap(deftrap2 (trap_init, TRAPFLAG_EXTRA_STACK, _T("libtrace_init"))); + dw(RTS); + + // patch SetFunction + setfunc_addr = here (); + calltrap (deftrap2 (trap_setfunc, TRAPFLAG_EXTRA_STACK, _T("libtrace_setfunc"))); + dw(RTS); + + // share trap for OldOpenLibrary/OpenLibrary + unsigned int open_lib_trap = deftrap2 (trap_openlib, TRAPFLAG_EXTRA_STACK, _T("libtrace_openlib")); + + // patch OldOpenLibrary + oldopenlib_addr = here (); + dw(JSR); + dl(0); // patch original call here + calltrap(open_lib_trap); + dw(RTS); + + // patch OpenLibrary + openlib_addr = here (); + dw(JSR); + dl(0); // patch original call here + calltrap(open_lib_trap); + dw(RTS); + + // pre-allocate call_pre traps for libs + for(int i=0;ifunc->info; + int pos = 0; + int len = max; + arg_info_t *arg = fi->args; + for(int i=0;inum_args;i++) { + // sep + if(i>0) { + if(len < 2) { + break; + } + *buf++ = ','; + *buf++ = ' '; + len -= 2; + } + + // name + int n = strlen(arg->name); + if(n > len) { + break; + } + strcpy(buf, arg->name); + buf += n; + len -= n; + + // register value + int reg = arg->reg; + uae_u32 val = ci->regs[reg]; + + // reg name + if(len < 5) { + break; + } + *buf++ = '['; + if(reg > 7) { + *buf++ = 'a'; + reg -= 8; + } else { + *buf++ = 'd'; + } + sprintf(buf, "%d", reg); + buf += 1; + *buf++ = ']'; + *buf++ = '='; + len -= 5; + + // value + if(len < 8) { + break; + } + sprintf(buf, "%08x", val); + buf += 8; + len -= 8; + + // next arg + arg++; + } + *buf = '\0'; +} + +static void show_dos_pkt(const char *prefix, uae_u32 ptr) +{ + uae_u32 dp_Type = get_long(ptr+8); + uae_u32 dp_Res1 = get_long(ptr+12); + uae_u32 dp_Res2 = get_long(ptr+16); + uae_u32 dp_Arg1 = get_long(ptr+20); + uae_u32 dp_Arg2 = get_long(ptr+24); + uae_u32 dp_Arg3 = get_long(ptr+28); + uae_u32 dp_Arg4 = get_long(ptr+32); + fprintf(out_fh, "%s [DosPkt: Type=%08x Args=%08x,%08x,%08x,%08x Res=%08x,%08x]\n", + prefix, dp_Type, dp_Arg1, dp_Arg2, dp_Arg3, dp_Arg4, dp_Res1, dp_Res2); +} + +static void handle_extra(const char *prefix, call_info_t *ci, int pre) +{ + if(ci->lib->tag == LIB_TAG_DOS) { + /* WaitPkt() -> d0 */ + if((ci->lvo == 252) && !pre) { + show_dos_pkt(prefix, ci->return_regs[0]); + } + } +} + +static void handle_call(call_info_t *ci, int pre) +{ + /* get func name or lvo */ + char buf[8]; + const char *fname = buf; + if(ci->func->info != NULL) { + fname = ci->func->info->name; + } else { + snprintf(buf, 7, "-%03d", ci->lvo); + } + + /* setup prefix string */ + char prefix[80]; + snprintf(prefix, 79, "@%08x %08x:%-16s %-10s ", + ci->callee_pc, ci->this_task, ci->task_name, ci->lib->short_name); + + if(pre) { + /* get arg string */ + char arg_buf[256]; + const char *args = "?"; + if(ci->func->info != NULL) { + gen_arg_str(ci, arg_buf, 255); + args = arg_buf; + } + + fprintf(out_fh, "%s %s(%s)\n", + prefix, fname, args); + } else { + /* post call: show return value */ + fprintf(out_fh, "%s %s -> d0=%08x\n", + prefix, fname, ci->return_regs[0]); + } + + /* extra output? */ + if(ci->func->flags & FUNC_FLAG_EXTRA) { + handle_extra(prefix, ci, pre); + } +} + +// ---------- TRAPS --------------------------------------------------------- + +/* third stage: resident is run with InitCode from autoconf of UAE board */ +static uae_u32 REGPARAM2 trap_init(TrapContext *ctx) +{ + /* patch exec funcs */ + exec_base = get_long(4); + printf("libtrace: init exec_base=%08x\n", exec_base); + + /* patch OldOpenLibrary */ + old_oldopenlib_addr = patch_SetFunction(ctx, exec_base, -408, openlib_addr); + + /* store old address */ + uae_u32 save_org = here(); + org(oldopenlib_addr+2); + dl(old_oldopenlib_addr); + org(save_org); + + /* patch OpenLibrary */ + old_openlib_addr = patch_SetFunction(ctx, exec_base, -552, openlib_addr); + + /* store old address */ + save_org = here(); + org(openlib_addr+2); + dl(old_openlib_addr); + org(save_org); + + /* finally patch SetFunction itself */ + old_setfunc_addr = patch_SetFunction(ctx, exec_base, -420, setfunc_addr); + + /* walk through initial lib list */ + uaecptr list = exec_base + 378; + uaecptr lib_base = get_long (list); + while (get_long(lib_base)) { + // get library name + char *lib_name = (char*)get_real_address(get_long(lib_base + 10)); + // do we have the lib? + lib_entry_t *lib = find_lib_by_name(lib_name); + if(lib != NULL) { + // setup lib for tracing + activate_libtrace(ctx, lib, lib_base); + } + + lib_base = get_long(lib_base); + } + + printf("libtrace: init done\n"); + return 0; +} + +/* SetFunction */ +static uae_u32 REGPARAM2 trap_setfunc(TrapContext *ctx) +{ + uae_u32 lib_base = m68k_areg(regs, 1); + uae_u32 raw_lvo = m68k_areg(regs, 0); + uae_u32 new_func = m68k_dreg(regs, 0); + + uae_s16 lvo = (uae_s16)(raw_lvo & 0xffff); + + uae_u32 lvo_addr = lib_base + lvo; + uae_u16 opcode = get_word(lvo_addr); + +#ifdef DEBUG_SETFUNC + /* get callee info */ + uae_u32 this_task = get_long(exec_base + 276); + uae_u32 sp = m68k_areg(regs, 7); + uae_u32 callee_pc = get_long(sp); + + printf("libtrace: @%08x t=%08x: SetFunction lib_base=%08x, lvo=%d, opcode=%04x, new_func=%08x -> ", + callee_pc, this_task, lib_base, lvo, opcode, new_func); +#endif + + /* is it a function we patched? */ + if((opcode & 0xf000) == 0xa000) { + /* Simulate SetFunction but do not touch trap before address! */ + uae_u32 old_func = get_long(lvo_addr+2); + pre_patch(ctx); + put_long(lvo_addr+2, new_func); + post_patch(ctx, lib_base); +#ifdef DEBUG_SETFUNC + printf("patched. old_func=%08x\n", old_func); +#endif + return old_func; + } + else { + /* lib is not patched: use regular SetFunction call */ + uae_u32 old_func = CallFunc(ctx, old_setfunc_addr); +#ifdef DEBUG_SETFUNC + printf("original. old_func=%08x\n", old_func); +#endif + return old_func; + } +} + +/* called after OpenLibrary(a1=libName) -> d0=lib_base or NULL */ +static uae_u32 REGPARAM2 trap_openlib(TrapContext *ctx) +{ + /* get lib_base */ + uae_u32 lib_base = m68k_dreg(regs, 0); + if(lib_base == 0) { + return 0; + } + + // get library name + char *lib_name = (char*)get_real_address(get_long(lib_base + 10)); + //printf("libtrace: openlib '%s'\n", lib_name); + + // do we have the lib patched already? + lib_entry_t *lib = find_lib_by_name(lib_name); + if(lib != NULL) { + if(lib->lib_base == 0) { + uae_u16 open_cnt = get_word(lib_base + 0x20); + printf("libtrace: patching '%s' open_cnt=%d\n", lib_name, open_cnt); + // setup lib for tracing + activate_libtrace(ctx, lib, lib_base); + } + } + + return lib_base; +} + +static void stack_push_addr(uae_u32 addr) +{ + uae_u32 sp = m68k_areg(regs, 7); + sp -= 4; + m68k_areg(regs, 7) = sp; + put_long(sp, addr); + //printf("@%08x: push addr %08x\n", sp, addr); +} + +static void stack_push_pointer(void *ptr) +{ + uae_u32 sp = m68k_areg(regs, 7); + sp -= sizeof(void *); + m68k_areg(regs, 7) = sp; + put_pointer(sp, ptr); + //printf("@%08x: push %p\n", sp, ptr); +} + +static void *stack_pop_pointer(void) +{ + uae_u32 sp = m68k_areg(regs, 7); + void *ptr = get_pointer(sp); + //printf("@%08x: pop %p\n", sp, ptr); + sp += sizeof(void *); + m68k_areg(regs, 7) = sp; + return ptr; +} + +/* trap: trace call */ +static uae_u32 REGPARAM2 trap_trace_call_pre(TrapContext *ctx) +{ + /* get trap code and func_ptr stored in jump table of lib */ + uae_u32 pc = m68k_getpc(); + uae_u16 trap_code = get_word(pc - 2); + uae_u32 func_ptr = get_long(pc); + + /* find context: lib and func structure */ + lib_entry_t *lib = find_lib_by_trap_code(trap_code); + if(lib == NULL) { + printf("libtrace: log: FATAL LIB==NULL! pc=%08x, func_ptr=%08x\n", pc, func_ptr); + stack_push_addr(func_ptr); + return 0; + } + + /* calc function offset */ + uae_s32 lvo = lib->lib_base - pc + 2; + int func_id = (lvo - 6) / 6; + if((func_id < 0) || (func_id >= lib->num_funcs)) { + printf("libtrace: log: FATAL INVALID FUNC ID! pc=%08x, func_ptr=%08x, lvo=%d, id=%d\n", + pc, func_ptr, lvo, func_id); + stack_push_addr(func_ptr); + return 0; + } + + /* check original function pointer */ + func_entry_t *func = &lib->funcs[func_id]; + uae_u32 func_addr = func->func_addr; + if(func_addr != pc) { + printf("libtrace: log: FATAL: wrong func entry?\n"); + stack_push_addr(func_ptr); + return 0; + } + + /* get some caller info */ + uae_u32 this_task = get_long(exec_base + 276); + uae_u32 sp = m68k_areg(regs, 7); + uae_u32 callee_pc = get_long(sp); + + /* get task name (if any) */ + char *my_task_name = (char*)get_real_address(get_long(this_task + 10)); + + /* filter by task name? */ + if((task_name != NULL) && (strcmp(task_name, my_task_name)!=0)) { + stack_push_addr(func_ptr); + return 0; + } + + /* create call info object */ + call_info_t *ci = (call_info_t *)malloc(sizeof(call_info_t)); + if(ci == NULL) { + printf("libtrace: log: FATAL: no memory!\n"); + stack_push_addr(func_ptr); + return 0; + } + + /* fill in call info */ + ci->lib = lib; + ci->func = func; + ci->lvo = lvo; + ci->this_task = this_task; + ci->callee_pc = callee_pc; + ci->func_ptr = func_ptr; + ci->task_name = (my_task_name != NULL) ? strdup(my_task_name) : ""; + for(int i=0;i<8;i++) { + ci->regs[i] = m68k_dreg(regs, i); + ci->regs[i+8] = m68k_areg(regs, i); + } + + /* report call before actual calling the function (pre) */ + handle_call(ci, 1); + + /* do we need to record return values? */ + int trace_return = (func->flags & FUNC_FLAG_VOID) == 0; + if(trace_return) { + /* push native call info pointer on m68k stack to get it in log_post call */ + stack_push_pointer(ci); + /* push log post address to be called after function returns with RTS */ + stack_push_addr(log_post_addr); + } else { + /* no post call -> free call info now */ + free((void *)ci->task_name); + free(ci); + } + + /* push func address on stack so the implicit RTS of trap will call it */ + stack_push_addr(func_ptr); + + /* pre debug? */ + if(func->flags & FUNC_FLAG_PRE_DEBUG) { + debug(); + } + + /* return value is ignored */ + return 0; +} + +/* trap: called after a library call if return values are requested */ +static uae_u32 REGPARAM2 trap_trace_call_post(TrapContext *ctx) +{ + /* retrieve call info from m68k stack */ + call_info_t *ci = (call_info_t *)stack_pop_pointer(); + + if(ci != NULL) { + /* get results in d0/d1 */ + for(int i=0;i<2;i++) { + ci->return_regs[i] = m68k_dreg(regs, i); + } + + /* report call after library call (post) */ + handle_call(ci, 0); + + /* post debug? */ + if(ci->func->flags & FUNC_FLAG_POST_DEBUG) { + debug(); + } + + /* free call info */ + free((void *)ci->task_name); + free(ci); + } + + /* return value is ignored */ + return 0; +} + +// ----------- API ---------- + +void libtrace_set_cfg_file(const char *new_cfg_file) +{ + if(cfg_file != NULL) { + free((void *)cfg_file); + cfg_file = NULL; + } + if(new_cfg_file != NULL) { + cfg_file = strdup(new_cfg_file); + } + reload = 1; +} + +const char *libtrace_get_cfg_file(void) +{ + return cfg_file; +} + +void libtrace_enable(int new_enable) +{ + enable = new_enable; +} + +int libtrace_is_enabled(void) +{ + return enable; +} + +void libtrace_set_task_name(const char *name) +{ + if(task_name != NULL) { + free((void *)task_name); + task_name = NULL; + } + if(name != NULL) { + task_name = strdup(name); + } +} + +const char *libtrace_get_task_name(void) +{ + return task_name; +} + +void libtrace_set_output_file(const char *new_out_file) +{ + // first close open file + if(out_fh != stdout) { + fclose(out_fh); + out_fh = stdout; + } + // free old + if(out_file != NULL) { + free((void *)out_file); + out_file = NULL; + } + // set new + if(new_out_file != NULL) { + out_file = strdup(new_out_file); + } +} + +const char *libtrace_get_output_file(void) +{ + return out_file; +} diff --git a/src/newcpu.cpp b/src/newcpu.cpp index 958aebd80..4a647b0e6 100644 --- a/src/newcpu.cpp +++ b/src/newcpu.cpp @@ -48,6 +48,10 @@ #include "jit/compemu.h" #include #endif + +/* allow traps (a-line) calls everywhere */ +int traps_everywhere = 0; + /* For faster JIT cycles handling */ uae_s32 pissoff = 0; @@ -1424,7 +1428,7 @@ static void build_cpufunctbl (void) instr *table = &table68k[opcode]; if (table->mnemo == i_ILLG) - continue; + continue; /* unimplemented opcode? */ if (table->unimpclev > 0 && lvl >= table->unimpclev) { @@ -2428,7 +2432,7 @@ static uae_u32 exception_pc (int nr) static void Exception_build_stack_frame (uae_u32 oldpc, uae_u32 currpc, uae_u32 ssw, int nr, int format) { int i; - + #if 0 if (nr < 24 || nr > 31) { // do not print debugging for interrupts write_log(_T("Building exception stack frame (format %X)\n"), format); @@ -2464,7 +2468,7 @@ static void Exception_build_stack_frame (uae_u32 oldpc, uae_u32 currpc, uae_u32 m68k_areg (regs, 7) -= 4; x_put_long (m68k_areg (regs, 7), regs.mmu_fault_addr); // FA - + m68k_areg (regs, 7) -= 2; x_put_word (m68k_areg (regs, 7), 0); m68k_areg (regs, 7) -= 2; @@ -2593,14 +2597,14 @@ static void Exception_mmu030 (int nr, uaecptr oldpc) exception_debug (nr); MakeSR (); - + if (!regs.s) { regs.usp = m68k_areg (regs, 7); m68k_areg(regs, 7) = regs.m ? regs.msp : regs.isp; regs.s = 1; mmu_set_super (1); } - + #if 0 if (nr < 24 || nr > 31) { // do not print debugging for interrupts write_log (_T("Exception_mmu030: Exception %i: %08x %08x %08x\n"), @@ -2634,7 +2638,7 @@ static void Exception_mmu030 (int nr, uaecptr oldpc) } else { Exception_build_stack_frame (oldpc, currpc, regs.mmu_ssw, nr, 0x0); } - + if (newpc & 1) { if (nr == 2 || nr == 3) cpu_halt (CPU_HALT_DOUBLE_FAULT); @@ -2672,7 +2676,7 @@ static void Exception_mmu (int nr, uaecptr oldpc) regs.s = 1; mmu_set_super (1); } - + newpc = x_get_long (regs.vbr + 4 * nr); #if 0 write_log (_T("Exception %d: %08x -> %08x\n"), nr, currpc, newpc); @@ -2696,7 +2700,7 @@ static void Exception_mmu (int nr, uaecptr oldpc) } else { Exception_build_stack_frame(oldpc, currpc, regs.mmu_ssw, nr, 0x0); } - + if (newpc & 1) { if (nr == 2 || nr == 3) cpu_halt (CPU_HALT_DOUBLE_FAULT); @@ -2717,9 +2721,9 @@ static void add_approximate_exception_cycles(int nr) return; if (nr >= 24 && nr <= 31) { /* Interrupts */ - cycles = 44 + 4; + cycles = 44 + 4; } else if (nr >= 32 && nr <= 47) { - /* Trap (total is 34, but cpuemux.c already adds 4) */ + /* Trap (total is 34, but cpuemux.c already adds 4) */ cycles = 34 -4; } else { switch (nr) @@ -3118,7 +3122,7 @@ static void m68k_reset2(bool hardreset) regs.caar = regs.cacr = 0; regs.itt0 = regs.itt1 = regs.dtt0 = regs.dtt1 = 0; regs.tcr = regs.mmusr = regs.urp = regs.srp = regs.buscr = 0; - mmu_tt_modified (); + mmu_tt_modified (); if (currprefs.cpu_model == 68020) { regs.cacr |= 8; set_cpu_caches (false); @@ -3202,7 +3206,7 @@ uae_u32 REGPARAM2 op_illg (uae_u32 opcode) return 4; } - if ((opcode & 0xF000) == 0xA000 && inrt) { + if ((opcode & 0xF000) == 0xA000 && (inrt || traps_everywhere)) { /* Calltrap. */ m68k_incpc_normal (2); m68k_handle_trap(opcode & 0xFFF); @@ -3742,7 +3746,7 @@ static int do_specialties (int cycles) { if (regs.spcflags & SPCFLAG_MODE_CHANGE) return 1; - + if (regs.spcflags & SPCFLAG_CHECK) { if (regs.halted) { if (haltloop()) @@ -4279,7 +4283,7 @@ static int do_specialties_thread(void) m68k_reset_delay = 0; unset_special(SPCFLAG_CHECK); } - + #ifdef JIT unset_special(SPCFLAG_END_COMPILE); /* has done its job */ #endif @@ -4766,7 +4770,7 @@ static void m68k_run_mmu030 (void) uaecptr new_addr = mmu030_translate(regs.instruction_pc, regs.s != 0, false, false); if (mmu030_fake_prefetch_addr != new_addr) { regs.opcode = mmu030_fake_prefetch; - write_log(_T("MMU030 fake prefetch remap: %04x, %08x -> %08x\n"), mmu030_fake_prefetch, mmu030_fake_prefetch_addr, new_addr); + write_log(_T("MMU030 fake prefetch remap: %04x, %08x -> %08x\n"), mmu030_fake_prefetch, mmu030_fake_prefetch_addr, new_addr); } else { if (mmu030_opcode_stageb < 0) { regs.opcode = x_prefetch (0); @@ -5027,7 +5031,7 @@ static void m68k_run_2ce (void) } (*cpufunctbl[r->opcode])(r->opcode); - + wait_memory_cycles(); cont: @@ -5683,7 +5687,7 @@ static void movemout (TCHAR *out, uae_u16 mask, int mode, int fpmode) if (mode == Apdi && !fpmode) { uae_u8 dmask2; uae_u8 amask2; - + amask2 = mask & 0xff; dmask2 = (mask >> 8) & 0xff; dmask = 0; @@ -5800,7 +5804,7 @@ void m68k_disasm_2 (TCHAR *buf, int bufsize, uaecptr pc, uaecptr *nextpc, int cn buf = buf_out (buf, &bufsize, _T("%08X "), pc); pc += 2; - + if (lookup->friendlyname) _tcscpy (instrname, lookup->friendlyname); else @@ -5937,7 +5941,7 @@ void m68k_disasm_2 (TCHAR *buf, int bufsize, uaecptr pc, uaecptr *nextpc, int cn int mode; int dreg = (extra >> 4) & 7; int regmask, fpmode; - + if (extra & 0x4000) { mode = (extra >> 11) & 3; regmask = extra & 0xff; // FMOVEM FPx diff --git a/src/patch.cpp b/src/patch.cpp index c3b92fbe1..0ba5ba4f9 100644 --- a/src/patch.cpp +++ b/src/patch.cpp @@ -71,7 +71,7 @@ void patch_func_init_post_call(TrapHandler func, patch_func *pf, int flags, int dw(RTS); } -static uae_u32 SetFunction(TrapContext *ctx, uae_u32 lib_base, uae_s16 offset, uae_u32 new_func) +uae_u32 patch_SetFunction(TrapContext *ctx, uae_u32 lib_base, uae_s16 offset, uae_u32 new_func) { uae_u32 old_a1 = m68k_areg(regs,1); uae_u32 old_a0 = m68k_areg(regs,0); @@ -109,7 +109,7 @@ void patch_func_set(TrapContext *ctx, uaecptr lib_base, uae_s16 func_lvo, patch_ } /* patch lib function */ - uae_u32 old_func = SetFunction(ctx, lib_base, func_lvo, pf->new_entry_addr); + uae_u32 old_func = patch_SetFunction(ctx, lib_base, func_lvo, pf->new_entry_addr); pf->old_entry_addr = old_func; /* store old pointer in rt_area */