Mimick icon indicating copy to clipboard operation
Mimick copied to clipboard

mmk_mock/mmk_when question

Open kugel- opened this issue 8 years ago • 5 comments

Hello,

I know this is very experimental, but we're playing around with mimick in the need for a decent mocking framework.

I'm mainly asking for clarification. I found that the mock is installed by calling mmk_mock(). It doesn't seem to matter when mmk_when() is called, so long it's called after mmk_mock().

Is this intended like this? I think it makes the API a bit awkward, if you want to install the mock in the middle of a test. It would seem more natural to have the mmk_when() call at the beginning of a scope, and making it effective with a later mmk_mock() call.

Also, maybe I'm missing something, but as of now mmk_mock() could also be folded into mmk_when(), and then calling mmk_when() when the mock should be installed.

Don't think of this as a bug report, I just want to understand mimick better, because it seems very useful.

kugel- avatar Mar 28 '17 12:03 kugel-

I have two more questions:

  1. Have you considered libffi for installing trampolines in a more portable manner (libffi supprts a vast number of architectures)?
  2. Can you install real functions for mocks instead of mmk_when()-sugar? The ability to return expressions and to modify arbitrary global variables seems limited inside mmk_when().

kugel- avatar Mar 28 '17 13:03 kugel-

The thing is that mmk_when is supposed to be called multiple times -- it just setups an input-output binding for an existing mock. Think of it as it inserting an if (actual_inputs == when_inputs) return when_output in the mock's code.

It might be a bit awkward when you have a really simple behaviour to describe, but when you want to make a mock that does something a bit more complicated depending on inputs, putting that complex handling in one big mmk_when makes things hard, when you could split that into multiple mmk_when.

As for your questions:

  1. I'll take a more in-depth look at libffi. The problem with mimick is that it has to both do this low-level plumbing while not being able to trust anyone (which is why mimick is redefining a few libc functions itself because it cannot assume that they aren't going to be mocked themselves). If libffi is using internally some functions that are probably going to be mocked, this is not going to work out.

  2. You could either just install a stub with mmk_stub_create using your function, or use the .then_call parameter in mmk_when. I'd recommend the first one if you don't want to specify additional behaviour with mmk_when, or don't care about verifying relationships with mmk_verify.

Snaipe avatar Mar 28 '17 13:03 Snaipe

ffi is really low-level too. I'm not aware that it calls libc functions besides mmap() and mprotect() to get writable, then executable sections. It goes great lengths for many architectures to install trampoline functions with any number of parameters and even variadic functions (the trampoline functions can call back into user code which can then introspect the parameters). It can also be linked statically.

Just saying, it would be help to get rid of assembler code and support more architectures.

kugel- avatar Mar 28 '17 14:03 kugel-

I'll send an email to their mailing list to get some more info. libffi seems to heavily rely on the assumption that callers know exactly the types & calling conventions of their functions are, which is sadly not the case for mimick -- we just forward a call to another function, preserving registers along the way as they are. I hope there's a way for me to use libffi for just that, but I don't know enough of the library yet.

Snaipe avatar Mar 28 '17 15:03 Snaipe

So I investigated some more on how I could "convince" libffi (or dyncall, for that matter), but I don't think this is going anywhere.

Fundamentally, the problem is very delicate for Mimick:

  • Mimick has to make sure that the user isn't mocking one of the functions it's using -- so I have to be extra careful when writing trampoline code.
  • The trampoline implementation must not call the mock function. It tries to be as invisible as possible, by not touching at all the registers that should have been passed to the real function. It even has to jmp, to avoid messing with the stack. In contrast, libffi/dyncall provide an interface to call foreign functions, which is not what we want.
  • The trampoline has no idea what the parameters are. We just know someone called us, and we have a bunch of registers that may or may have some significance as parameters. So, all in all, I can't build a function call with libffi, since they need those informations.
  • Mimick's trampoline must prepare the mock context before handing control to the mock function. Even if I tried to integrate libffi to do my bidding, I would still end up in square one with the problem of preparing the libffi procedure to call, which is even more intrusive for the trampoline.

All in all, libffi & dyncall both address the problem of calling foreign functions, but that's not the problem we are trying to solve with trampolines in Mimick. It's a shame because that means that I'll have to maintain & test a bunch of different architectures, but I don't know any other project that does this.

Snaipe avatar Jul 13 '17 18:07 Snaipe