Spying on Embedded ‘asm’ directives

Sometimes embedded developers have to use inline assembler instructions to get better control of the processor, or to improve performance. How should we deal with those when we’re doing TDD and testing off the target?

What’s the problem? The embedded asm statements cause compilation errors if the assembler instructions are not part of the off-target test platform instruction set. Also some of the instructions might not be legal in the test environment. This article shows how to insert a test double for the asm directives with gcc and CppUTest.


In this example, I’ll assume that whatever the asm instructions were supposed to do, that they are not needed to run in the test environment. We’ll look at two alternatives: ignoring asm directives and spying on them.

Here is example production code using the asm directive:

//snip...
do
{
  asm("NOP");
  asm("FLOP");
  asm("POP");
  status = IORead(0);
} while ((status & ReadyBit) == 0);

For some reason NOP, FLOP and POP need to be executed before an I/O read operation. in the unit test environment we don’t want them assembled or executed.

The simplest thing to do is to make asm go away with the preprocessor. We can force the preprocessor to include a file that makes asm go away. If you are using the CppUTest makefile system you can add this preprocessor directive:

  CPPUTEST_CFLAGS += -include mocks/NullAsm.h

NullAsm.h would simply be this:

#define asm(x)

This macro makes asm and its parameters go away. There might be a gcc command line option to ignore asm also, but I could not find one.

But maybe you want more than to ignore the asm directives. Let’s spy on them. AsmSpy captures the assembly instructions so that the test can check that the right instructions are specified to execute.

Let’s look at the tests for the AsmSpy.

#include "CppUTest/TestHarness.h"
 
TEST_GROUP(AsmSpy)
{
    void setup()
    {
      AsmSpy_Create(20);
    }
 
    void teardown()
    {
       AsmSpy_Destroy();
    }
};
 
TEST(AsmSpy, CaptureAsmInstructions)
{
    AsmSpy("NOP");
    AsmSpy("FLOP");
    AsmSpy("POP");
    STRCMP_EQUAL("NOP;FLOP;POP;", AsmSpy_Debrief());
}

In setup the spy is created and given a capacity. This spy will remember a string of 20 characters of assembler. In the TEST, you can see that the spy separates each assembly string with a semicolon.

Now we’ve got to sneak the spy into my code. We do this with the preprocessor because asm is a keyword and we need text replacement. We can force the gcc compiler to include AsmSpy.h like this:

  CPPUTEST_CFLAGS = -include mocks/AsmSpy.h

This directive causes the AsmSpy to be the first include file of the compilied .c file. We don’t edit in the AsmSpy because we don’t want any evidence of the spy in the production code. Here is AsmSpy.h.

#ifndef D_AsmSpy_H
#define D_AsmSpy_H
 
#define asm AsmSpy
 
void AsmSpy_Create(int size);
void AsmSpy_Destroy(void);
void AsmSpy(const char *);
const char * AsmSpy_Debrief(void);
 
#endif

Notice #define asm AsmSpy. This causes asm directives to be converted to AsmSpy calls when we build for test. The production code only needs the AsmSpy prototype, but it does no harm to have the spy’s full interface.

AsmSpy.c looks like this:

#include "AsmSpy.h"
#include <string.h>
 
static char * instructions = 0;
static int capacity = 0;
 
void AsmSpy_Create(int cap)
{
    capacity = cap;
    instructions = malloc(capacity+1);
    instructions[0] = 0;
}
 
void AsmSpy_Destroy(void)
{
    free(instructions);
}
 
void AsmSpy(const char * i)
{
    if (strlen(i) + strlen(instructions) &lt; capacity)
    {
        strcat(instructions, i);
        strcat(instructions, ";");
    }
}
 
const char * AsmSpy_Debrief(void)
{
    return instructions;
}

Being that the asm code is machine dependent, we could also isolate this asm code in a separate machine dependent function, and then override that function with a linker test double. Then the preprocessor substitution would not be needed. I might go this route if the asm has to return some value, or I was using Extended Asm.

One thought on “Spying on Embedded ‘asm’ directives

  1. Pingback: Hiding Non-standard C Keywords for Off-Target Testing « James Grenning’s Blog

Leave a Reply

Your email address will not be published.

Be gone spammers * Time limit is exhausted. Please reload the CAPTCHA.