Home | History | Annotate | Download | only in tsan
      1 /* Copyright (c) 2008-2010, Google Inc.
      2  * All rights reserved.
      3  *
      4  * Redistribution and use in source and binary forms, with or without
      5  * modification, are permitted provided that the following conditions are
      6  * met:
      7  *
      8  *     * Redistributions of source code must retain the above copyright
      9  * notice, this list of conditions and the following disclaimer.
     10  *     * Neither the name of Google Inc. nor the names of its
     11  * contributors may be used to endorse or promote products derived from
     12  * this software without specific prior written permission.
     13  *
     14  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
     15  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
     16  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
     17  * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
     18  * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
     19  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
     20  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
     21  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
     22  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
     23  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
     24  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
     25  */
     26 
     27 // This file is part of ThreadSanitizer, a dynamic data race detector.
     28 
     29 // Some parts of the code in this file are taken from the examples
     30 // in DynamoRIO distribution, which have the following copyright.
     31 /* **********************************************************
     32  * Copyright (c) 2003-2008 VMware, Inc.  All rights reserved.
     33  * **********************************************************/
     34 
     35 /*
     36  * Redistribution and use in source and binary forms, with or without
     37  * modification, are permitted provided that the following conditions are met:
     38  *
     39  * * Redistributions of source code must retain the above copyright notice,
     40  *   this list of conditions and the following disclaimer.
     41  *
     42  * * Redistributions in binary form must reproduce the above copyright notice,
     43  *   this list of conditions and the following disclaimer in the documentation
     44  *   and/or other materials provided with the distribution.
     45  *
     46  * * Neither the name of VMware, Inc. nor the names of its contributors may be
     47  *   used to endorse or promote products derived from this software without
     48  *   specific prior written permission.
     49  *
     50  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
     51  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
     52  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
     53  * ARE DISCLAIMED. IN NO EVENT SHALL VMWARE, INC. OR CONTRIBUTORS BE LIABLE
     54  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
     55  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
     56  * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
     57  * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
     58  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
     59  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
     60  * DAMAGE.
     61  */
     62 
     63 // Author: Konstantin Serebryany.
     64 // Author: Timur Iskhodzhanov.
     65 //
     66 // ******* WARNING ********
     67 // This code is experimental. Do not expect anything here to work.
     68 // ***** END WARNING ******
     69 
     70 #include "dr_api.h"
     71 
     72 #include "ts_util.h"
     73 
     74 #define EXTRA_REPLACE_PARAMS
     75 #define REPORT_READ_RANGE(a,b)
     76 #define REPORT_WRITE_RANGE(a,b)
     77 #include "ts_replace.h"
     78 
     79 #define Printf dr_printf
     80 
     81 static void *g_lock;
     82 static int   g_n_created_threads;
     83 
     84 typedef unordered_map<intptr_t, string> SymbolsTable;
     85 static SymbolsTable *sym_tab;
     86 
     87 string *g_main_module_path;
     88 
     89 //--------------- StackFrame ----------------- {{{1
     90 struct StackFrame {
     91   uintptr_t pc;
     92   uintptr_t sp;
     93   StackFrame(uintptr_t p, uintptr_t s) : pc(p), sp(s) { }
     94 };
     95 
     96 
     97 //--------------- DrThread ----------------- {{{1
     98 struct DrThread {
     99   int tid;  // A unique 0-based thread id.
    100   vector<StackFrame> shadow_stack;
    101 };
    102 
    103 static DrThread &GetCurrentThread(void *drcontext) {
    104   return *(DrThread*)dr_get_tls_field(drcontext);
    105 }
    106 
    107 //--------------- ShadowStack ----------------- {{{1
    108 #define DEB_PR (0 && t.tid == 1)
    109 
    110 static void PrintShadowStack(DrThread &t) {
    111   Printf("T%d Shadow stack (%d)\n", t.tid, (int)t.shadow_stack.size());
    112   for (int i = t.shadow_stack.size() - 1; i >= 0; i--) {
    113     uintptr_t pc = t.shadow_stack[i].pc;
    114     Printf("%s[%p]\n", g_main_module_path->c_str(), pc);
    115   }
    116   for (int i = t.shadow_stack.size() - 1; i >= 0; i--) {
    117     uintptr_t pc = t.shadow_stack[i].pc;
    118     uintptr_t sp = t.shadow_stack[i].sp;
    119     Printf("  sp=%p pc=%p\n", sp, pc);
    120   }
    121 }
    122 
    123 static void UpdateShadowStack(DrThread &t, uintptr_t sp) {
    124   while (t.shadow_stack.size() > 0 && sp >= t.shadow_stack.back().sp) {
    125     t.shadow_stack.pop_back();
    126     if (DEB_PR) {
    127       dr_mutex_lock(g_lock);
    128       Printf("T%d PopShadowStack\n", t.tid);
    129       PrintShadowStack(t);
    130       dr_mutex_unlock(g_lock);
    131     }
    132   }
    133 }
    134 
    135 static void PushShadowStack(DrThread &t, uintptr_t pc, uintptr_t target_pc, uintptr_t sp) {
    136   if (t.shadow_stack.size() > 0) {
    137     t.shadow_stack.back().pc = pc;
    138   }
    139   t.shadow_stack.push_back(StackFrame(target_pc, sp));
    140   if (DEB_PR) {
    141     dr_mutex_lock(g_lock);
    142     Printf("T%d PushShadowStack %p %p %d\n", t.tid, pc, target_pc, sp);
    143     PrintShadowStack(t);
    144     dr_mutex_unlock(g_lock);
    145   }
    146 }
    147 
    148 //--------------- callbacks ----------------- {{{1
    149 static void OnEvent_ThreadInit(void *drcontext) {
    150   DrThread *t_ptr = new DrThread;
    151   DrThread &t = *t_ptr;
    152 
    153   dr_mutex_lock(g_lock);
    154   t.tid = g_n_created_threads++;
    155   dr_mutex_unlock(g_lock);
    156 
    157   dr_set_tls_field(drcontext, t_ptr);
    158 
    159   dr_printf("T%d %s\n", t.tid, (char*)__FUNCTION__+8);
    160 }
    161 
    162 static void OnEvent_ThreadExit(void *drcontext) {
    163   DrThread &t = GetCurrentThread(drcontext);
    164   dr_printf("T%d %s\n", t.tid, (char*)__FUNCTION__+8);
    165 }
    166 
    167 void OnEvent_ModuleLoaded(void *drcontext, const module_data_t *info,
    168                           bool loaded) {
    169   // if this assertion fails, your DynamoRIO is too old. You need rev261 with some patches...
    170   CHECK(info->full_path);
    171 
    172   dr_printf("%s: %s (%s)\n", __FUNCTION__,
    173             dr_module_preferred_name(info), info->full_path);
    174   if (g_main_module_path == NULL) {
    175     g_main_module_path = new string(info->full_path);
    176   }
    177 }
    178 
    179 static void OnEvent_Exit(void) {
    180   dr_printf("ThreadSanitizerDynamoRio: done\n");
    181   dr_mutex_destroy(g_lock);
    182 }
    183 
    184 static void On_Mop(uintptr_t pc, size_t size, void *a, bool is_w) {
    185   void *drcontext = dr_get_current_drcontext();
    186   DrThread &t = GetCurrentThread(drcontext);
    187   if (t.tid == 777) {
    188     dr_fprintf(STDERR, "T%d pc=%p a=%p size=%ld %s\n", t.tid, pc, a, size, is_w ? "WRITE" : "READ");
    189   }
    190 }
    191 
    192 static void On_Read(uintptr_t pc, size_t size, void *a) {
    193   On_Mop(pc, size, a, false);
    194 }
    195 
    196 static void On_Write(uintptr_t pc, size_t size, void *a) {
    197   On_Mop(pc, size, a, true);
    198 }
    199 
    200 static void On_AnyCall(uintptr_t pc, uintptr_t target_pc, uintptr_t sp, bool is_direct) {
    201   void *drcontext = dr_get_current_drcontext();
    202   DrThread &t = GetCurrentThread(drcontext);
    203   // dr_fprintf(STDOUT, "T%d CALL %p => %p; sp=%p\n", t.tid, pc, target_pc, sp);
    204   PushShadowStack(t, pc, target_pc, sp);
    205 }
    206 
    207 static void On_DirectCall(uintptr_t pc, uintptr_t target_pc, uintptr_t sp) {
    208   On_AnyCall(pc, target_pc, sp, true);
    209 }
    210 
    211 static void On_IndirectCall(uintptr_t pc, uintptr_t target_pc, uintptr_t sp) {
    212   On_AnyCall(pc, target_pc, sp, false);
    213 }
    214 
    215 static void On_TraceEnter(uintptr_t pc, uintptr_t sp) {
    216   void *drcontext = dr_get_current_drcontext();
    217   DrThread &t = GetCurrentThread(drcontext);
    218   // dr_fprintf(STDOUT, "T%d TRACE:\n%p\n%p\n", t.tid, pc, sp);
    219   UpdateShadowStack(t, sp);
    220 }
    221 
    222 //--------------- instrumentation ----------------- {{{1
    223 opnd_t opnd_create_base_disp_from_dst(opnd_t dst) {
    224   return opnd_create_base_disp(opnd_get_base(dst),
    225                                opnd_get_index(dst),
    226                                opnd_get_scale(dst),
    227                                opnd_get_disp(dst),
    228                                OPSZ_lea);
    229 }
    230 
    231 static void InstrumentOneMop(void* drcontext, instrlist_t *bb,
    232                              instr_t *instr, opnd_t opnd, bool is_w) {
    233   //   opnd_disassemble(drcontext, opnd, 1);
    234   //   dr_printf("  -- (%s opnd)\n", is_w ? "write" : "read");
    235   void *callback = (void*)(is_w ? On_Write : On_Read);
    236   int size = opnd_size_in_bytes(opnd_get_size(opnd));
    237 
    238   instr_t *tmp_instr = NULL;
    239   reg_id_t reg = REG_XAX;
    240 
    241   /* save %xax */
    242   dr_save_reg(drcontext, bb, instr, reg, SPILL_SLOT_2);
    243 
    244   if (opnd_is_base_disp(opnd)) {
    245     /* lea opnd => %xax */
    246     opnd_set_size(&opnd, OPSZ_lea);
    247     tmp_instr = INSTR_CREATE_lea(drcontext,
    248                                  opnd_create_reg(reg),
    249                                  opnd);
    250   } else if(
    251 #ifdef X86_64
    252       opnd_is_rel_addr(opnd) ||
    253 #endif
    254       opnd_is_abs_addr(opnd)) {
    255     tmp_instr = INSTR_CREATE_mov_imm(drcontext,
    256                                      opnd_create_reg(reg),
    257                                      OPND_CREATE_INTPTR(opnd_get_addr(opnd)));
    258   }
    259   if (tmp_instr) {
    260     // CHECK(tmp_instr);
    261     instrlist_meta_preinsert(bb, instr, tmp_instr);
    262 
    263     /* clean call */
    264     dr_insert_clean_call(drcontext, bb, instr, callback, false,
    265                          3,
    266                          OPND_CREATE_INTPTR(instr_get_app_pc(instr)),
    267                          OPND_CREATE_INT32(size),
    268                          opnd_create_reg(reg));
    269     /* restore %xax */
    270     dr_restore_reg(drcontext, bb, instr, REG_XAX, SPILL_SLOT_2);
    271   } else {
    272     dr_printf("%s ????????????????????\n", __FUNCTION__);
    273   }
    274 }
    275 
    276 static void InstrumentMopInstruction(void *drcontext,
    277                                      instrlist_t *bb, instr_t *instr) {
    278   // reads:
    279   for (int a = 0; a < instr_num_srcs(instr); a++) {
    280     opnd_t curop = instr_get_src(instr, a);
    281     if (opnd_is_memory_reference(curop)) {
    282       InstrumentOneMop(drcontext, bb, instr, curop, false);
    283     }
    284   }
    285   // writes:
    286   for (int a = 0; a < instr_num_dsts(instr); a++) {
    287     opnd_t curop = instr_get_dst(instr, a);
    288     if (opnd_is_memory_reference(curop)) {
    289       InstrumentOneMop(drcontext, bb, instr, curop, true);
    290     }
    291   }
    292   //dr_printf("reads: %d writes: %d\n", n_reads, n_writes);
    293 }
    294 
    295 static void InstrumentInstruction(void *drcontext, instrlist_t *bb,
    296                                   instr_t *instr) {
    297   // instr_disassemble(drcontext, instr, 1);
    298   // dr_printf("  -- \n");
    299   if (instr_is_call_direct(instr)) {
    300     dr_insert_call_instrumentation(drcontext, bb, instr,
    301                                    (app_pc)On_DirectCall);
    302   } else if (instr_is_call_indirect(instr)) {
    303     dr_insert_mbr_instrumentation(drcontext, bb, instr,
    304                                   (app_pc)On_IndirectCall, SPILL_SLOT_1);
    305 
    306   } else if (instr_reads_memory(instr) || instr_writes_memory(instr)) {
    307     InstrumentMopInstruction(drcontext, bb, instr);
    308   }
    309 }
    310 
    311 static dr_emit_flags_t OnEvent_Trace(void *drcontext, void *tag,
    312                                      instrlist_t *trace, bool translating) {
    313   instr_t *first_instr = NULL;
    314   for (instr_t *instr = instrlist_first(trace); instr != NULL;
    315        instr = instr_get_next(instr)) {
    316     if (instr_get_app_pc(instr)) {
    317       first_instr = instr;
    318       break;
    319     }
    320   }
    321   if (first_instr) {
    322     // instr_disassemble(drcontext, first_instr, 1);
    323     // dr_printf("  -- in_trace %p\n", instr_get_app_pc(first_instr));
    324     dr_insert_clean_call(drcontext, trace, first_instr,
    325                          (void*)On_TraceEnter, false,
    326                          2,
    327                          OPND_CREATE_INTPTR(instr_get_app_pc(first_instr)),
    328                          opnd_create_reg(REG_XSP)
    329                          );
    330   }
    331   return DR_EMIT_DEFAULT;
    332 }
    333 
    334 int replace_foo(int i, int j, int k) {
    335   dr_printf(" dy 'foo_replace'(%i, %i, %i)\n", i, j, k);
    336   return 1;
    337 }
    338 
    339 typedef unordered_map<intptr_t, void*> FunctionsReplaceMap;
    340 static FunctionsReplaceMap *fun_replace_map;
    341 
    342 namespace wrap {
    343 
    344 int (*orig_foo)(int,int,int) = NULL;
    345 int in_wrapper = 0;  // TODO: Make it thread-local
    346 
    347 static int wrapped_foo(int i, int j, int k) {
    348   in_wrapper = 1;
    349 
    350   dr_printf(" dy 'foo_wrap'(%i, %i, %i)\n", i, j, k);
    351   dr_printf("orig_foo = %p\n", orig_foo);
    352   int ret = 13;
    353   if (orig_foo != NULL)
    354     ret = orig_foo(i, j, k) + 4200;
    355   else
    356     dr_printf("ERROR! orig_foo is not set!\n");/**/
    357 
    358   in_wrapper = 0;
    359   return ret;
    360 }
    361 
    362 int is_in_wrapper(int arg) {
    363   // TODO: this may not work well with recursive functions
    364   return in_wrapper;
    365 }
    366 }
    367 
    368 void print_bb(void* drcontext, instrlist_t *bb, const char * desc) {
    369   dr_printf("==================\n");
    370   dr_printf("%s:\n", desc);
    371   for (instr_t *i = instrlist_first(bb); i != NULL; i = instr_get_next(i)) {
    372     instr_disassemble(drcontext, i, 1);
    373     dr_printf("\n");
    374   }
    375   dr_printf("==================\n");
    376 }
    377 
    378 static dr_emit_flags_t OnEvent_BB(void* drcontext, void *tag, instrlist_t *bb,
    379                                   bool for_trace, bool translating) {
    380   instr_t *first_instr = instrlist_first(bb);
    381   app_pc pc = instr_get_app_pc(first_instr);
    382   string symbol_name = "UNKNOWN";
    383   if (sym_tab->find((intptr_t)pc) != sym_tab->end()) {
    384     symbol_name = (*sym_tab)[(intptr_t)pc];
    385     //dr_printf("Symbol = %s\n", symbol_name.c_str());
    386   }
    387 
    388   if (fun_replace_map->count((intptr_t)pc) > 0) {
    389     // Replace client function with the function supplied by the tool.
    390     // The logic is inspired by drmemory/replace.c
    391     app_pc target_fun = (app_pc)(*fun_replace_map)[(intptr_t)pc];
    392     const module_data_t *info = dr_lookup_module(pc);
    393     dr_printf("REDIR: %s (from %s) redirected to %p\n",
    394               symbol_name.c_str(), info->full_path, target_fun);
    395 
    396     instrlist_clear(drcontext, bb);
    397     instrlist_append(bb, INSTR_XL8(INSTR_CREATE_jmp(drcontext, opnd_create_pc(target_fun)), pc));
    398   } else {
    399     if (StringMatch("*foo_to_wrap*", symbol_name)) {
    400       const module_data_t *info = dr_lookup_module(pc);
    401       dr_printf(" 'foo_to_wrap' entry point: bb %p, %s / %s\n", pc, dr_module_preferred_name(info), info->full_path);
    402       wrap::orig_foo = (int (*)(int,int,int))(void*)pc;
    403 
    404       //print_bb(drcontext, bb, "BEFORE");
    405       // TODO: Use something more optimized than clean_call
    406       dr_insert_clean_call(drcontext, bb, first_instr, (void*)wrap::is_in_wrapper,
    407                            false, 1, OPND_CREATE_INTPTR(pc));
    408       instr_t *opr_instr = INSTR_CREATE_test(drcontext, opnd_create_reg(REG_XAX),
    409                                                         opnd_create_reg(REG_XAX));
    410       instr_t *jne_instr = INSTR_CREATE_jcc(drcontext, OP_jz,
    411                                             opnd_create_pc((app_pc)wrap::wrapped_foo));
    412       instrlist_meta_preinsert(bb, first_instr, opr_instr);
    413       instrlist_meta_preinsert(bb, first_instr, jne_instr);
    414 
    415       //print_bb(drcontext, bb, "AFTER");
    416     }
    417 
    418     instr_t *instr, *next_instr;
    419     for (instr = instrlist_first(bb); instr != NULL; instr = next_instr) {
    420       next_instr = instr_get_next(instr);
    421       if (instr_get_app_pc(instr))  // don't instrument non-app code
    422         InstrumentInstruction(drcontext, bb, instr);
    423     }
    424 
    425 
    426     OnEvent_Trace(drcontext, tag, bb, translating);
    427   }
    428 
    429   return DR_EMIT_DEFAULT;
    430 }
    431 
    432 void ReadSymbolsTableFromFile(const char *filename) {
    433   file_t f = dr_open_file(filename, DR_FILE_READ);
    434   CHECK(f != INVALID_FILE);
    435 
    436   const int BUFF_SIZE = 1 << 16;  // should be enough for testing
    437   char buff[BUFF_SIZE];
    438   dr_read_file(f, buff, BUFF_SIZE);
    439   char *cur_line = buff;
    440   while (*cur_line) {
    441     char *next_line = strstr(cur_line, "\n");
    442     if (next_line != NULL)
    443       *next_line = 0;
    444     char fun_name[1024];
    445     char dummy;
    446     void* pc;
    447     sscanf(cur_line, "%p %c %s", &pc, &dummy, fun_name);
    448     //dr_printf("%s => %p\n", fun_name, pc);
    449     (*sym_tab)[(intptr_t)pc] = fun_name;
    450 
    451     if (next_line == NULL) break;
    452     cur_line = next_line + 1;
    453   }
    454 
    455 }
    456 
    457 void ReplaceFunc3(void *img, void *rtn, string filter, void *fun_ptr) {
    458   for (SymbolsTable::iterator i = sym_tab->begin(); i != sym_tab->end(); i++) {
    459     if (StringMatch(filter, i->second))
    460       (*fun_replace_map)[(intptr_t)i->first] = fun_ptr;
    461   }
    462 }
    463 
    464 //--------------- dr_init ----------------- {{{1
    465 DR_EXPORT void dr_init(client_id_t id) {
    466   sym_tab = new SymbolsTable;
    467 
    468   // HACK doesn't work if multiple options are passed.
    469   const char *opstr = dr_get_options(id);
    470   dr_printf("Options: %s\n", opstr);
    471   const char *fname = strstr(opstr, "--symbols=");
    472   if (fname) {
    473     ReadSymbolsTableFromFile(fname + 10);
    474   }
    475 
    476   // Register events.
    477   dr_register_exit_event(OnEvent_Exit);
    478   dr_register_bb_event(OnEvent_BB);
    479   dr_register_trace_event(OnEvent_Trace);
    480   dr_register_thread_init_event(OnEvent_ThreadInit);
    481   dr_register_thread_exit_event(OnEvent_ThreadExit);
    482   dr_register_module_load_event(OnEvent_ModuleLoaded);
    483   g_lock = dr_mutex_create();
    484 
    485   fun_replace_map = new FunctionsReplaceMap();
    486   void *img = NULL, *rtn = NULL;
    487   #define AFUNPTR void*
    488   ReplaceFunc3(img, rtn, "memchr", (AFUNPTR)Replace_memchr);
    489   ReplaceFunc3(img, rtn, "strchr", (AFUNPTR)Replace_strchr);
    490   ReplaceFunc3(img, rtn, "index", (AFUNPTR)Replace_strchr);
    491   ReplaceFunc3(img, rtn, "strchrnul", (AFUNPTR)Replace_strchrnul);
    492   ReplaceFunc3(img, rtn, "strrchr", (AFUNPTR)Replace_strrchr);
    493   ReplaceFunc3(img, rtn, "rindex", (AFUNPTR)Replace_strrchr);
    494   ReplaceFunc3(img, rtn, "strlen", (AFUNPTR)Replace_strlen);
    495   ReplaceFunc3(img, rtn, "memcpy", (AFUNPTR)Replace_memcpy);
    496   ReplaceFunc3(img, rtn, "memmove", (AFUNPTR)Replace_memmove);
    497   ReplaceFunc3(img, rtn, "memcmp", (AFUNPTR)Replace_memcmp);
    498   ReplaceFunc3(img, rtn, "strcpy", (AFUNPTR)Replace_strcpy);
    499   ReplaceFunc3(img, rtn, "stpcpy", (AFUNPTR)Replace_stpcpy);
    500   ReplaceFunc3(img, rtn, "strncpy", (AFUNPTR)Replace_strncpy);
    501   ReplaceFunc3(img, rtn, "strcmp", (AFUNPTR)Replace_strcmp);
    502   ReplaceFunc3(img, rtn, "strncmp", (AFUNPTR)Replace_strncmp);
    503   ReplaceFunc3(img, rtn, "strcat", (AFUNPTR)Replace_strcat);
    504   ReplaceFunc3(img, rtn, "*foo_to_replace*", (AFUNPTR)replace_foo);
    505 }
    506 // end. {{{1
    507 // vim:shiftwidth=2:softtabstop=2:expandtab
    508