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;
StackFrameStackFrame93 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
GetCurrentThread(void * drcontext)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
PrintShadowStack(DrThread & t)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
UpdateShadowStack(DrThread & t,uintptr_t sp)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
PushShadowStack(DrThread & t,uintptr_t pc,uintptr_t target_pc,uintptr_t sp)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
OnEvent_ThreadInit(void * drcontext)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
OnEvent_ThreadExit(void * drcontext)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
OnEvent_ModuleLoaded(void * drcontext,const module_data_t * info,bool loaded)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
OnEvent_Exit(void)179 static void OnEvent_Exit(void) {
180 dr_printf("ThreadSanitizerDynamoRio: done\n");
181 dr_mutex_destroy(g_lock);
182 }
183
On_Mop(uintptr_t pc,size_t size,void * a,bool is_w)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
On_Read(uintptr_t pc,size_t size,void * a)192 static void On_Read(uintptr_t pc, size_t size, void *a) {
193 On_Mop(pc, size, a, false);
194 }
195
On_Write(uintptr_t pc,size_t size,void * a)196 static void On_Write(uintptr_t pc, size_t size, void *a) {
197 On_Mop(pc, size, a, true);
198 }
199
On_AnyCall(uintptr_t pc,uintptr_t target_pc,uintptr_t sp,bool is_direct)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
On_DirectCall(uintptr_t pc,uintptr_t target_pc,uintptr_t sp)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
On_IndirectCall(uintptr_t pc,uintptr_t target_pc,uintptr_t sp)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
On_TraceEnter(uintptr_t pc,uintptr_t sp)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
opnd_create_base_disp_from_dst(opnd_t dst)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
InstrumentOneMop(void * drcontext,instrlist_t * bb,instr_t * instr,opnd_t opnd,bool is_w)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
InstrumentMopInstruction(void * drcontext,instrlist_t * bb,instr_t * instr)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
InstrumentInstruction(void * drcontext,instrlist_t * bb,instr_t * instr)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
OnEvent_Trace(void * drcontext,void * tag,instrlist_t * trace,bool translating)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
replace_foo(int i,int j,int k)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
wrapped_foo(int i,int j,int k)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
is_in_wrapper(int arg)362 int is_in_wrapper(int arg) {
363 // TODO: this may not work well with recursive functions
364 return in_wrapper;
365 }
366 }
367
print_bb(void * drcontext,instrlist_t * bb,const char * desc)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
OnEvent_BB(void * drcontext,void * tag,instrlist_t * bb,bool for_trace,bool translating)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
ReadSymbolsTableFromFile(const char * filename)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
ReplaceFunc3(void * img,void * rtn,string filter,void * fun_ptr)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
dr_init(client_id_t id)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