Snaipe / Mimick

A KISS, cross-platform C mocking library
MIT License
152 stars 43 forks source link

Crash in libcriterion when mocking stdlib.h's exit function #24

Open CamJN opened 3 years ago

CamJN commented 3 years ago

I'm getting a crash in libcriterion when trying to use Mimick to mock stdlib.h's exit function.

#include <criterion/criterion.h>
#include <criterion/parameterized.h>
#include <criterion/redirect.h>
#include <mimick.h>

struct Options {
  int skip;
  pid_t pid;
  bool nulls;
};
void parseArgs(int argc, char** argv, struct Options* options) {
  perror(argv[0]);
  exit(EXIT_FAILURE);
}

mmk_mock_define(exit_mock, void, int);

struct parseArgsTestCase { int argc; char** argv; struct Options options; };

char *cr_strdup(const char *str) {
  char *ptr = cr_malloc(sizeof(char) * (strlen(str) + 1));
  if (ptr)
    strcpy(ptr, str);
  return ptr;
}

void redirect_all_std_and_mock_exit(void) {
  cr_redirect_stdout();
  cr_redirect_stderr();
  mmk_mock("exit@self", exit_mock);
}
void unmock_exit(void){
  mmk_reset(exit);
}
void free_parse_args(struct criterion_test_params *crp) {
  struct parseArgsTestCase* params = (struct parseArgsTestCase*) crp->params;
  for (size_t i = 0; i < crp->length; ++i) {
    char **strings = params[i].argv;
    for (size_t j = 0; j < params[i].argc; ++j) {
      cr_free(strings[i]);
    }
    cr_free(strings);
  }
  cr_free(params);
}

ParameterizedTestParameters(parseArgs, failure) {
  size_t nb_params = 3;
  pid_t pid = 1;
  struct parseArgsTestCase* params = cr_malloc(sizeof (struct parseArgsTestCase) * nb_params);

  params[0].argc = 1;
  params[0].argv = cr_malloc(sizeof(char*) * 1);
  params[0].argv[0] = cr_strdup("progname");

  params[1].argc = 0;
  params[1].argv = NULL;

  params[2].argc = 2;
  params[2].argv = cr_malloc(sizeof(char*) * 2);
  params[2].argv[0] = cr_strdup("progname");
  params[2].argv[1] = cr_strdup("-0");

  return cr_make_param_array(struct parseArgsTestCase, params, nb_params, free_parse_args);
}

ParameterizedTest(struct parseArgsTestCase* param, parseArgs, failure, .init = redirect_all_std_and_mock_exit, .fini = unmock_exit) {
  struct Options actual = {.skip=0, .pid=0, .nulls=true};

  parseArgs(param->argc, param->argv, &actual);

  mmk_verify(exit(mmk_eq(size_t, EXIT_FAILURE)), .times = 1);
}
PATH=/usr/local/opt/llvm/bin/:$PATH CRITERION_TEST_PATTERN='parseArgs*failure' bin/tests --debug=idle
[----] Criterion v2.3.3
[====] Running 1 test from parseArgs:
[----] parseArgs::failure: Started test has PID 60151.
[RUN ] parseArgs::failure
$ lldb -p 60151
(lldb) process attach --pid 60151
Process 60151 stopped
* thread #1, queue = 'com.apple.main-thread', stop reason = signal SIGSTOP
    frame #0: 0x00007fff20464462 libsystem_kernel.dylib`__pthread_kill + 10
libsystem_kernel.dylib`__pthread_kill:
->  0x7fff20464462 <+10>: jae    0x7fff2046446c            ; <+20>
    0x7fff20464464 <+12>: movq   %rax, %rdi
    0x7fff20464467 <+15>: jmp    0x7fff2045e6a1            ; cerror_nocancel
    0x7fff2046446c <+20>: retq   
Target 0: (tests) stopped.

Executable module set to "~/Developer/C++/getargv/test/bin/tests".
Architecture set to: x86_64h-apple-macosx-.
(lldb) bt all
* thread #1, queue = 'com.apple.main-thread', stop reason = signal SIGSTOP
  * frame #0: 0x00007fff20464462 libsystem_kernel.dylib`__pthread_kill + 10
    frame #1: 0x00007fff20492610 libsystem_pthread.dylib`pthread_kill + 263
    frame #2: 0x00007fff20374ba3 libsystem_c.dylib`raise + 26
    frame #3: 0x000000010c966db4 libcriterion.3.dylib`bxfi_term_sandbox_ctx + 113
    frame #4: 0x000000010c96829d libcriterion.3.dylib`bxfi_main + 84
    frame #5: 0x00007fff204ad621 libdyld.dylib`start + 1
(lldb) c
Process 60151 resuming
Process 60151 stopped
* thread #1, queue = 'com.apple.main-thread', stop reason = EXC_BAD_ACCESS (code=EXC_I386_GPFLT)
    frame #0: 0x00007fff204ac3ee libdyld.dylib`stack_not_16_byte_aligned_error
libdyld.dylib`stack_not_16_byte_aligned_error:
->  0x7fff204ac3ee <+0>: movdqa %xmm0, (%rsp)
    0x7fff204ac3f3 <+5>: int3   

libdyld.dylib`_dyld_fast_stub_entry:
    0x7fff204ac3f4 <+0>: pushq  %rbp
    0x7fff204ac3f5 <+1>: movq   %rsp, %rbp
Target 0: (tests) stopped.

I'm almost certainly doing something wrong, but on the off chance I'm not, here's my report.

CamJN commented 3 years ago

Simplified the test case a bunch more:

#include <criterion/criterion.h>
#include <mimick.h>

mmk_mock_define(exit_mock, void, int);

void mock_exit(void) {
  mmk_mock("exit@self", exit_mock);
}
void unmock_exit(void){
  mmk_reset(exit);
}

Test(parseArgs, failure, .init = mock_exit, .fini = unmock_exit) {

  exit(EXIT_FAILURE);

  mmk_verify(exit(mmk_eq(int, EXIT_FAILURE)), .times = 1);
}

I still get a fault, but possibly not an alignment fault:

Process 89175 resuming
Process 89175 stopped
* thread #1, queue = 'com.apple.main-thread', stop reason = EXC_BAD_ACCESS (code=2, address=0x7ffeee1adf50)
    frame #0: 0x00007ffeee1adf50
Target 0: (test) stopped.
(lldb) bt
* thread #1, queue = 'com.apple.main-thread', stop reason = EXC_BAD_ACCESS (code=2, address=0x7ffeee1adf50)
  * frame #0: 0x00007ffeee1adf50
    frame #1: 0x0000000101a62294 libcriterion.3.dylib`criterion_internal_test_main + 107
    frame #2: 0x0000000101a62294 libcriterion.3.dylib`criterion_internal_test_main + 107
    frame #3: 0x0000000101a54295 test`parseArgs_failure_jmp at test.c:14:1
    frame #4: 0x0000000101a619bc libcriterion.3.dylib`run_test_child + 772
    frame #5: 0x0000000101a702a3 libcriterion.3.dylib`bxfi_main + 90
    frame #6: 0x00007fff20532f3d libdyld.dylib`start + 1
    frame #7: 0x00007fff20532f3d libdyld.dylib`start + 1
CamJN commented 3 years ago

Removing criterion, and only trying to use mimick makes it clear it is an alignment error:

#include <stdlib.h>
#include <mimick.h>

mmk_mock_define(exit_mock, void, int);

int main(void) {
  mmk_mock("exit@self", exit_mock);

  exit(EXIT_FAILURE);

  mmk_reset(exit);
  return 0;
}
lldb ./test
(lldb) target create "./test"
Current executable set to '/Users/camdennarzt/Developer/C/test/test' (x86_64).
(lldb) r
Process 77726 launched: '/Users/camdennarzt/Developer/C/test/test' (x86_64)
Process 77726 stopped
* thread #1, queue = 'com.apple.main-thread', stop reason = EXC_BAD_ACCESS (code=EXC_I386_GPFLT)
    frame #0: 0x00007fff204fac9e libdyld.dylib`stack_not_16_byte_aligned_error
libdyld.dylib`stack_not_16_byte_aligned_error:
->  0x7fff204fac9e <+0>: movdqa %xmm0, (%rsp)
    0x7fff204faca3 <+5>: int3   
    0x7fff204faca4 <+6>: nop    
    0x7fff204faca5 <+7>: nop    
Target 0: (test) stopped.
(lldb) bt
* thread #1, queue = 'com.apple.main-thread', stop reason = EXC_BAD_ACCESS (code=EXC_I386_GPFLT)
  * frame #0: 0x00007fff204fac9e libdyld.dylib`stack_not_16_byte_aligned_error
    frame #1: 0x00007ffeefbfef98
    frame #2: 0x0000000100001951 test`mmk_mock_create_internal + 289
    frame #3: 0x0000000100003325 test`mmkuser_exit_mock_create(tgt="\x90&", opts=(sentinel_ = 0, noabort = 0)) at test.c:4:1
    frame #4: 0x000000010014a010
    frame #5: 0x00007fff204fbf3d libdyld.dylib`start + 1
(lldb) up
frame #1: 0x00007ffeefbfef98
->  0x7ffeefbfef98: callq  0x7ffeeeafaf8c
    0x7ffeefbfef9d: jg     0x7ffeefbfef9f
    0x7ffeefbfef9f: addb   %dl, 0x19(%rcx)
    0x7ffeefbfefa2: addb   %al, (%rax)
(lldb) up
frame #2: 0x0000000100001951 test`mmk_mock_create_internal + 289
test`mmk_mock_create_internal:
->  0x100001951 <+289>: movq   %rax, 0x8(%r14)
    0x100001955 <+293>: testq  %rax, %rax
    0x100001958 <+296>: je     0x100001987               ; <+343>
    0x10000195a <+298>: testb  %r15b, %r15b
(lldb)
CamJN commented 3 years ago

even the sample test crashes:

$ lldb mimick/build/sample/strdup/strdup_test 
(lldb) target create "mimick/build/sample/strdup/strdup_test"
Current executable set to '/Users/camdennarzt/Developer/C/test/mimick/build/sample/strdup/strdup_test' (x86_64).
(lldb) r
Process 79619 launched: '/Users/camdennarzt/Developer/C/test/mimick/build/sample/strdup/strdup_test' (x86_64)
Process 79619 stopped
* thread #1, queue = 'com.apple.main-thread', stop reason = EXC_BAD_ACCESS (code=1, address=0x8)
    frame #0: 0x0000000100005e9e strdup_test`mmk_mock_destroy_internal + 14
strdup_test`mmk_mock_destroy_internal:
->  0x100005e9e <+14>: movq   0x8(%rdi), %rdi
    0x100005ea2 <+18>: testq  %rdi, %rdi
    0x100005ea5 <+21>: je     0x100005ec1               ; <+49>
    0x100005eab <+27>: nopl   (%rax,%rax)
Target 0: (strdup_test) stopped.
(lldb) bt
* thread #1, queue = 'com.apple.main-thread', stop reason = EXC_BAD_ACCESS (code=1, address=0x8)
  * frame #0: 0x0000000100005e9e strdup_test`mmk_mock_destroy_internal + 14
    frame #1: 0x00000001000050fb strdup_test`test_simple_case + 539
    frame #2: 0x00000001000052b4 strdup_test`main + 20
    frame #3: 0x00007fff204fbf3d libdyld.dylib`start + 1
    frame #4: 0x00007fff204fbf3d libdyld.dylib`start + 1
(lldb)