sclog.c 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612
  1. /*
  2. * sclog: the DynamoRIO instrumentation system that goes with the
  3. * PuTTY test binary 'testsc'.
  4. *
  5. * For general discussion and build instructions, see the comment at
  6. * the top of testsc.c.
  7. */
  8. #include <inttypes.h>
  9. #include <string.h>
  10. #include "dr_api.h"
  11. #include "drmgr.h"
  12. #include "drsyms.h"
  13. #include "drreg.h"
  14. #include "drutil.h"
  15. #include "drwrap.h"
  16. /*
  17. * The file we're currently logging to, if any.
  18. */
  19. static file_t outfile = INVALID_FILE;
  20. /*
  21. * A counter which we can increment and decrement around any library
  22. * function we don't want to log the details of what happens inside.
  23. * Mainly this is for memory allocation functions, which will diverge
  24. * control depending on the progress of their search for something
  25. * they can allocate.
  26. */
  27. size_t logging_paused = 0;
  28. /*
  29. * This log message appears at the start of whatever DynamoRIO
  30. * considers a 'basic block', i.e. a sequence of instructions with no
  31. * branches. Logging these is cheaper than logging every single
  32. * instruction, and should still be adequate to detect any divergence
  33. * of control flow.
  34. */
  35. static void log_pc(const char *loc)
  36. {
  37. if (outfile == INVALID_FILE || logging_paused)
  38. return;
  39. dr_fprintf(outfile, "%s: start basic block\n", loc);
  40. }
  41. /*
  42. * Hardware division instructions are unlikely to run in time
  43. * independent of the data, so we log both their parameters.
  44. */
  45. static void log_div(uint n, uint d, const char *loc)
  46. {
  47. if (outfile == INVALID_FILE || logging_paused)
  48. return;
  49. dr_fprintf(outfile, "%s: divide %"PRIuMAX" / %"PRIuMAX"\n",
  50. loc, (uintmax_t)n, (uintmax_t)d);
  51. }
  52. /*
  53. * Register-controlled shift instructions are not reliably one cycle
  54. * long on all platforms, so we log the shift couhnt.
  55. */
  56. static void log_var_shift(uint sh, const char *loc)
  57. {
  58. if (outfile == INVALID_FILE || logging_paused)
  59. return;
  60. dr_fprintf(outfile, "%s: var shift by %"PRIuMAX"\n", loc, (uintmax_t)sh);
  61. }
  62. /*
  63. * We need to log memory accesses, so as to detect data-dependent
  64. * changes in the access pattern (e.g. incautious use of a lookup
  65. * table). But one thing we _can't_ control for perfectly is that in
  66. * two successive runs of the same crypto primitive, malloc may be
  67. * called, and may return different addresses - which of course is not
  68. * dependent on the data (unless the size of the allocated block
  69. * does).
  70. *
  71. * So we track all the memory allocations that happen during logging,
  72. * and any addresses accessed within those blocks are logged as
  73. * something along the lines of 'n bytes from the start of the mth
  74. * allocation'.
  75. *
  76. * Allocations that happened before a given log file was opened are
  77. * not tracked. The program under test will ensure that any of those
  78. * used by the primitive are at the same address in all runs anyway.
  79. */
  80. struct allocation {
  81. /*
  82. * We store the list of allocations in a linked list, so we can
  83. * look them up by address, and delete them as they're freed.
  84. *
  85. * A balanced binary search tree would be faster, but this is
  86. * easier to get right first time!
  87. */
  88. struct allocation *prev, *next;
  89. uintptr_t start, size, index;
  90. };
  91. static struct allocation alloc_ends[1] = { alloc_ends, alloc_ends, 0, 0, 0 };
  92. static uintptr_t next_alloc_index = 0;
  93. static void free_allocation(struct allocation *alloc)
  94. {
  95. alloc->next->prev = alloc->prev;
  96. alloc->prev->next = alloc->next;
  97. dr_global_free(alloc, sizeof(struct allocation));
  98. }
  99. /*
  100. * Wrap the log_set_file() function in testsc.c, and respond to it by
  101. * opening or closing log files.
  102. */
  103. static void wrap_logsetfile(void *wrapctx, void **user_data)
  104. {
  105. if (outfile) {
  106. dr_close_file(outfile);
  107. outfile = INVALID_FILE;
  108. }
  109. const char *outfilename = drwrap_get_arg(wrapctx, 0);
  110. if (outfilename) {
  111. outfile = dr_open_file(outfilename, DR_FILE_WRITE_OVERWRITE);
  112. DR_ASSERT(outfile != INVALID_FILE);
  113. }
  114. /*
  115. * Reset the allocation list to empty, whenever we open or close a
  116. * log file.
  117. */
  118. while (alloc_ends->next != alloc_ends)
  119. free_allocation(alloc_ends->next);
  120. next_alloc_index = 0;
  121. }
  122. /*
  123. * Wrap the dry_run() function in testsc.c, to tell it we're here.
  124. */
  125. static void wrap_dryrun(void *wrapctx, void *user_data)
  126. {
  127. drwrap_set_retval(wrapctx, (void *)0);
  128. }
  129. /*
  130. * Look up the memory allocation record corresponding to an address.
  131. */
  132. static struct allocation *find_allocation(const void *ptr)
  133. {
  134. uintptr_t address = (uintptr_t)ptr;
  135. for (struct allocation *alloc = alloc_ends->next;
  136. alloc != alloc_ends; alloc = alloc->next) {
  137. if (alloc && address - alloc->start < alloc->size)
  138. return alloc;
  139. }
  140. return NULL;
  141. }
  142. /*
  143. * Log a memory access.
  144. */
  145. static void log_mem(app_pc addr, uint size, uint write, const char *loc)
  146. {
  147. if (outfile == INVALID_FILE || logging_paused)
  148. return;
  149. struct allocation *alloc = find_allocation((const void *)addr);
  150. if (!alloc) {
  151. dr_fprintf(outfile, "%s: %s %"PRIuMAX" @ %"PRIxMAX"\n",
  152. loc, write ? "store" : "load", (uintmax_t)size,
  153. (uintmax_t)addr);
  154. } else {
  155. dr_fprintf(outfile, "%s: %s %"PRIuMAX" @ allocations[%"PRIuPTR"]"
  156. " + %"PRIxMAX"\n",
  157. loc, write ? "store" : "load", (uintmax_t)size,
  158. alloc->index, (uintmax_t)(addr - alloc->start));
  159. }
  160. }
  161. /*
  162. * Record the allocation of some memory. (Common code between malloc
  163. * and realloc.)
  164. */
  165. static void allocated(void *ptr, size_t size)
  166. {
  167. if (outfile == INVALID_FILE)
  168. return; /* no need to track allocations outside a logging interval */
  169. struct allocation *alloc = dr_global_alloc(sizeof(struct allocation));
  170. alloc->start = (uintptr_t)ptr;
  171. alloc->size = size;
  172. alloc->index = next_alloc_index++;
  173. alloc->prev = alloc_ends->prev;
  174. alloc->next = alloc_ends;
  175. alloc->prev->next = alloc->next->prev = alloc;
  176. }
  177. /*
  178. * Record that memory has been freed. Note that we may free something
  179. * that was allocated when we weren't logging, so we must cope with
  180. * find_allocation returning NULL.
  181. */
  182. static void freed(void *ptr)
  183. {
  184. struct allocation *alloc = find_allocation(ptr);
  185. if (alloc)
  186. free_allocation(alloc);
  187. }
  188. /*
  189. * The actual wrapper functions for malloc, realloc and free.
  190. */
  191. static void wrap_malloc_pre(void *wrapctx, void **user_data)
  192. {
  193. logging_paused++;
  194. *user_data = drwrap_get_arg(wrapctx, 0);
  195. dr_fprintf(outfile, "malloc %"PRIuMAX"\n", (uintmax_t)*user_data);
  196. }
  197. static void wrap_aligned_alloc_pre(void *wrapctx, void **user_data)
  198. {
  199. logging_paused++;
  200. size_t align = (size_t) drwrap_get_arg(wrapctx, 0);
  201. *user_data = drwrap_get_arg(wrapctx, 1);
  202. dr_fprintf(outfile, "aligned_alloc align=%zu size=%"PRIuMAX"\n",
  203. align, (uintmax_t)*user_data);
  204. }
  205. static void wrap_free_pre(void *wrapctx, void **user_data)
  206. {
  207. logging_paused++;
  208. void *ptr = drwrap_get_arg(wrapctx, 0);
  209. freed(ptr);
  210. }
  211. static void wrap_realloc_pre(void *wrapctx, void **user_data)
  212. {
  213. logging_paused++;
  214. void *ptr = drwrap_get_arg(wrapctx, 0);
  215. freed(ptr);
  216. *user_data = drwrap_get_arg(wrapctx, 1);
  217. dr_fprintf(outfile, "realloc %"PRIuMAX"\n", (uintmax_t)*user_data);
  218. }
  219. static void wrap_alloc_post(void *wrapctx, void *user_data)
  220. {
  221. void *ptr = drwrap_get_retval(wrapctx);
  222. if (!ptr)
  223. return;
  224. size_t size = (size_t)user_data;
  225. allocated(ptr, size);
  226. logging_paused--;
  227. }
  228. /*
  229. * Common post-wrapper function to unpause the logging.
  230. */
  231. static void unpause_post(void *wrapctx, void *user_data)
  232. {
  233. logging_paused--;
  234. }
  235. /*
  236. * Make a string representation of the address of an instruction,
  237. * including a function name and/or a file+line combination if
  238. * possible. These will be logged alongside every act of interest
  239. * where we can make one.
  240. */
  241. static void instr_format_location(instr_t *instr, char **outloc)
  242. {
  243. app_pc addr = (app_pc)instr_get_app_pc(instr);
  244. char location[2048], symbol[512], fileline[1024];
  245. bool got_sym = false, got_line = false;
  246. if (*outloc)
  247. return;
  248. symbol[0] = '\0';
  249. fileline[0] = '\0';
  250. module_data_t *data = dr_lookup_module(addr);
  251. if (data) {
  252. drsym_info_t sym;
  253. char file[MAXIMUM_PATH];
  254. sym.struct_size = sizeof(sym);
  255. sym.name = symbol;
  256. sym.name_size = sizeof(symbol);
  257. sym.file = file;
  258. sym.file_size = sizeof(file);
  259. drsym_error_t status = drsym_lookup_address(
  260. data->full_path, addr - data->start, &sym, DRSYM_DEFAULT_FLAGS);
  261. got_line = (status == DRSYM_SUCCESS);
  262. got_sym = got_line || status == DRSYM_ERROR_LINE_NOT_AVAILABLE;
  263. if (got_line)
  264. snprintf(fileline, sizeof(fileline), " = %s:%"PRIu64,
  265. file, (uint64_t)sym.line);
  266. }
  267. snprintf(location, sizeof(location),
  268. "%"PRIx64"%s%s%s",
  269. (uint64_t)addr, got_sym ? " = " : "", got_sym ? symbol : "",
  270. fileline);
  271. size_t len = strlen(location) + 1;
  272. char *loc = dr_global_alloc(len);
  273. memcpy(loc, location, len);
  274. *outloc = loc;
  275. }
  276. /*
  277. * Function that tests a single operand of an instruction to see if
  278. * it's a memory reference, and if so, adds a call to log_mem.
  279. */
  280. static void try_mem_opnd(
  281. void *drcontext, instrlist_t *bb, instr_t *instr, char **loc,
  282. opnd_t opnd, bool write)
  283. {
  284. if (!opnd_is_memory_reference(opnd))
  285. return;
  286. instr_format_location(instr, loc);
  287. reg_id_t r0, r1;
  288. drreg_status_t st;
  289. st = drreg_reserve_register(drcontext, bb, instr, NULL, &r0);
  290. DR_ASSERT(st == DRREG_SUCCESS);
  291. st = drreg_reserve_register(drcontext, bb, instr, NULL, &r1);
  292. DR_ASSERT(st == DRREG_SUCCESS);
  293. bool ok = drutil_insert_get_mem_addr(drcontext, bb, instr, opnd, r0, r1);
  294. DR_ASSERT(ok);
  295. uint size = drutil_opnd_mem_size_in_bytes(opnd, instr);
  296. dr_insert_clean_call(
  297. drcontext, bb, instr, (void *)log_mem, false,
  298. 4, opnd_create_reg(r0), OPND_CREATE_INT32(size),
  299. OPND_CREATE_INT32(write), OPND_CREATE_INTPTR(*loc));
  300. st = drreg_unreserve_register(drcontext, bb, instr, r1);
  301. DR_ASSERT(st == DRREG_SUCCESS);
  302. st = drreg_unreserve_register(drcontext, bb, instr, r0);
  303. DR_ASSERT(st == DRREG_SUCCESS);
  304. }
  305. /*
  306. * The main function called to instrument each machine instruction.
  307. */
  308. static dr_emit_flags_t instrument_instr(
  309. void *drcontext, void *tag, instrlist_t *bb, instr_t *instr,
  310. bool for_trace, bool translating, void *user_data)
  311. {
  312. char *loc = NULL;
  313. /*
  314. * If this instruction is the first in its basic block, call
  315. * log_pc to record that we're executing this block at all.
  316. */
  317. if (drmgr_is_first_instr(drcontext, instr)) {
  318. instr_format_location(instr, &loc);
  319. dr_insert_clean_call(
  320. drcontext, bb, instr, (void *)log_pc, false,
  321. 1, OPND_CREATE_INTPTR(loc));
  322. }
  323. /*
  324. * If the instruction reads or writes memory, log its access.
  325. */
  326. if (instr_reads_memory(instr) || instr_writes_memory(instr)) {
  327. for (int i = 0, limit = instr_num_srcs(instr); i < limit; i++)
  328. try_mem_opnd(drcontext, bb, instr, &loc,
  329. instr_get_src(instr, i), instr_writes_memory(instr));
  330. for (int i = 0, limit = instr_num_dsts(instr); i < limit; i++)
  331. try_mem_opnd(drcontext, bb, instr, &loc,
  332. instr_get_dst(instr, i), instr_writes_memory(instr));
  333. }
  334. /*
  335. * Now do opcode-specific checks.
  336. */
  337. int opcode = instr_get_opcode(instr);
  338. switch (opcode) {
  339. #if defined(X86)
  340. case OP_div:
  341. case OP_idiv:
  342. /*
  343. * x86 hardware divisions. The operand order for DR's
  344. * representation of these seem to be: 0 = denominator, 1 =
  345. * numerator MSW, 2 = numerator LSW.
  346. */
  347. instr_format_location(instr, &loc);
  348. dr_insert_clean_call(
  349. drcontext, bb, instr, (void *)log_div, false,
  350. 3, instr_get_src(instr, 2), instr_get_src(instr, 0),
  351. OPND_CREATE_INTPTR(loc));
  352. break;
  353. #endif
  354. #if defined(AARCH64)
  355. case OP_sdiv:
  356. case OP_udiv:
  357. /*
  358. * AArch64 hardware divisions. 0 = numerator, 1 = denominator.
  359. */
  360. instr_format_location(instr, &loc);
  361. dr_insert_clean_call(
  362. drcontext, bb, instr, (void *)log_div, false,
  363. 3, instr_get_src(instr, 0), instr_get_src(instr, 1),
  364. OPND_CREATE_INTPTR(loc));
  365. break;
  366. #endif
  367. #if defined(X86)
  368. case OP_shl:
  369. case OP_shr:
  370. case OP_sar:
  371. case OP_shlx:
  372. case OP_shrx:
  373. case OP_sarx:
  374. case OP_rol:
  375. case OP_ror:
  376. case OP_rcl:
  377. case OP_rcr: {
  378. /*
  379. * Shift instructions. If they're register-controlled, log the
  380. * shift count.
  381. */
  382. opnd_t shiftcount = instr_get_src(instr, 0);
  383. if (!opnd_is_immed(shiftcount)) {
  384. reg_id_t r0;
  385. drreg_status_t st;
  386. st = drreg_reserve_register(drcontext, bb, instr, NULL, &r0);
  387. DR_ASSERT(st == DRREG_SUCCESS);
  388. opnd_t op_r0 = opnd_create_reg(r0);
  389. instr_t *movzx = INSTR_CREATE_movzx(drcontext, op_r0, shiftcount);
  390. instr_set_translation(movzx, instr_get_app_pc(instr));
  391. instrlist_preinsert(bb, instr, movzx);
  392. instr_format_location(instr, &loc);
  393. dr_insert_clean_call(
  394. drcontext, bb, instr, (void *)log_var_shift, false,
  395. 2, op_r0, OPND_CREATE_INTPTR(loc));
  396. st = drreg_unreserve_register(drcontext, bb, instr, r0);
  397. DR_ASSERT(st == DRREG_SUCCESS);
  398. }
  399. break;
  400. }
  401. #endif
  402. #if defined(AARCH64)
  403. case OP_lslv:
  404. case OP_asrv:
  405. case OP_lsrv:
  406. case OP_rorv: {
  407. /*
  408. * AArch64 variable shift instructions.
  409. */
  410. opnd_t shiftcount = instr_get_src(instr, 1);
  411. DR_ASSERT(opnd_is_reg(shiftcount));
  412. reg_id_t shiftreg = opnd_get_reg(shiftcount);
  413. if (shiftreg >= DR_REG_W0 && shiftreg <= DR_REG_WSP)
  414. shiftreg = reg_32_to_64(shiftreg);
  415. instr_format_location(instr, &loc);
  416. dr_insert_clean_call(
  417. drcontext, bb, instr, (void *)log_var_shift, false,
  418. 2, opnd_create_reg(shiftreg), OPND_CREATE_INTPTR(loc));
  419. break;
  420. }
  421. #endif
  422. }
  423. return DR_EMIT_DEFAULT;
  424. }
  425. static void exit_event(void)
  426. {
  427. if (outfile != INVALID_FILE) {
  428. dr_fprintf(outfile, "exit while recording enabled\n");
  429. dr_close_file(outfile);
  430. outfile = INVALID_FILE;
  431. }
  432. drsym_exit();
  433. drreg_exit();
  434. drwrap_exit();
  435. drutil_exit();
  436. drmgr_exit();
  437. }
  438. /*
  439. * We ask DR to expand any x86 string instructions like REP MOVSB, so
  440. * that we can log all the individual memory accesses without getting
  441. * confused.
  442. */
  443. static dr_emit_flags_t expand_rep_movsb(
  444. void *drcontext, void *tag, instrlist_t *bb, bool for_trace,
  445. bool translating)
  446. {
  447. bool ok = drutil_expand_rep_string(drcontext, bb);
  448. DR_ASSERT(ok);
  449. return DR_EMIT_DEFAULT;
  450. }
  451. typedef void (*prewrapper_t)(void *wrapctx, void **user_data);
  452. typedef void (*postwrapper_t)(void *wrapctx, void *user_data);
  453. /*
  454. * Helper function for bulk use of drwrap.
  455. */
  456. static void try_wrap_fn(const module_data_t *module, const char *name,
  457. prewrapper_t pre, postwrapper_t post, bool *done)
  458. {
  459. if (*done)
  460. return;
  461. size_t offset;
  462. drsym_error_t status = drsym_lookup_symbol(
  463. module->full_path, name, &offset, DRSYM_DEFAULT_FLAGS);
  464. if (status == DRSYM_SUCCESS) {
  465. app_pc notify_fn = module->start + offset;
  466. bool ok = drwrap_wrap(notify_fn, pre, post);
  467. DR_ASSERT(ok);
  468. *done = true;
  469. }
  470. }
  471. /*
  472. * When each module (e.g. shared library) is loaded, try to wrap all
  473. * the functions we care about. For each one, we keep a static bool
  474. * that will stop us trying again once we've found it the first time.
  475. */
  476. static void load_module(
  477. void *drcontext, const module_data_t *module, bool loaded)
  478. {
  479. bool libc = !strncmp(dr_module_preferred_name(module), "libc", 4);
  480. #define TRY_WRAP(fn, pre, post) do \
  481. { \
  482. static bool done_this_one = false; \
  483. try_wrap_fn(module, fn, pre, post, &done_this_one); \
  484. } while (0)
  485. if (loaded) {
  486. TRY_WRAP("log_to_file_real", wrap_logsetfile, NULL);
  487. TRY_WRAP("dry_run_real", NULL, wrap_dryrun);
  488. if (libc) {
  489. TRY_WRAP("malloc", wrap_malloc_pre, wrap_alloc_post);
  490. TRY_WRAP("aligned_alloc", wrap_aligned_alloc_pre, wrap_alloc_post);
  491. TRY_WRAP("realloc", wrap_realloc_pre, wrap_alloc_post);
  492. TRY_WRAP("free", wrap_free_pre, unpause_post);
  493. /*
  494. * More strangely named versions of standard C library
  495. * functions, which I've observed in practice to be where the
  496. * calls end up. I think these are probably selected by
  497. * STT_IFUNC in libc.so, so that the normally named version of
  498. * the function is never reached at all.
  499. *
  500. * This list is not expected to be complete. If you re-run
  501. * this test on a different platform and find control flow
  502. * diverging inside some libc function that looks as if it's
  503. * another name for malloc or memset or whatever, then you may
  504. * need to add more aliases here to stop the test failing.
  505. */
  506. TRY_WRAP("__GI___libc_malloc", wrap_malloc_pre, wrap_alloc_post);
  507. TRY_WRAP("__libc_malloc", wrap_malloc_pre, wrap_alloc_post);
  508. TRY_WRAP("__GI___libc_realloc", wrap_realloc_pre, wrap_alloc_post);
  509. TRY_WRAP("__GI___libc_free", wrap_free_pre, unpause_post);
  510. TRY_WRAP("cfree", wrap_free_pre, unpause_post);
  511. }
  512. }
  513. }
  514. /*
  515. * Main entry point that sets up all the facilities we need.
  516. */
  517. DR_EXPORT void dr_client_main(client_id_t id, int argc, const char **argv)
  518. {
  519. dr_set_client_name(
  520. "Time-sensitive activity logger for PuTTY crypto testing",
  521. "https://www.chiark.greenend.org.uk/~sgtatham/putty/");
  522. outfile = INVALID_FILE;
  523. bool ok = drmgr_init();
  524. DR_ASSERT(ok);
  525. /*
  526. * Run our main instrumentation pass with lower priority than
  527. * drwrap, so that we don't start logging the inside of a function
  528. * whose drwrap pre-wrapper would have wanted to disable logging.
  529. */
  530. drmgr_priority_t pri = {sizeof(pri), "sclog", NULL, NULL,
  531. DRMGR_PRIORITY_INSERT_DRWRAP+1};
  532. ok = drmgr_register_bb_instrumentation_event(
  533. NULL, instrument_instr, &pri);
  534. DR_ASSERT(ok);
  535. ok = drutil_init();
  536. DR_ASSERT(ok);
  537. ok = drwrap_init();
  538. DR_ASSERT(ok);
  539. drsym_error_t symstatus = drsym_init(0);
  540. DR_ASSERT(symstatus == DRSYM_SUCCESS);
  541. dr_register_exit_event(exit_event);
  542. drreg_options_t ops = { sizeof(ops), 3, false };
  543. drreg_status_t regstatus = drreg_init(&ops);
  544. DR_ASSERT(regstatus == DRREG_SUCCESS);
  545. drmgr_register_module_load_event(load_module);
  546. ok = drmgr_register_bb_app2app_event(expand_rep_movsb, NULL);
  547. DR_ASSERT(ok);
  548. }