Debugging Webassembly With Print-Statements – Hexdump Addition

Dumping hex values is a very important addition to our Webassembly debug infrastructure. Hexdump (the dumping of hex values) is helpful when you need to see the content of buffers. I am currently working on implementing SHA256 for Webassembly, which is the next step in this blog roadmap, and felt the need for the HexDump function to help me to figure out why my SHA256 was not working.

The HexDump complements the previous Webassembly debug implementation, check it out for more details.

How it Looks Like?

The following demo shows how the Hexdump behaves. The input (abc) and resulting buffers can be seen each line contains data in hex and ascii formats:

HexDump-Webassembly-Implementation

HexDump Implementation

Below you can see its implementation. It is not difficult but it is a little bit tricky. Basically a buffer called line is built, line‘s first part prints the buffer bytes converted to hex values and its second part prints the buffer bytes converted to ascii. Each of these parts have its own indexes(idx_hex and idx_ascii), which makes the code much more readable. The code is commented in detail, take a look at it, please let me know if something is unclear or need fixing:

Macro to be added to debug-wasm.h, for details look here.

This is the macro used to dump hex values and not the C function, doing this way we can log information like filename, line, etc…


#define DBG_DUMP(level, addr, len, fmt, ...) \
do \
{ \
    if (DEBUG_WASM_ENABLED) \
    { \
        Dbg_Dump(level, addr, len,"%s:%d:%s(): " fmt, __FILE__, __LINE__, __func__, __VA_ARGS__); \
    } \
} while (0)

C function to be added to debug-wasm.c, for details look here.


//
// Dump memory
//
void Dbg_Dump(const DEBUG_WASM_LEVELS level, const void *addr, const size_t len, const char *fmt, ...)
{
    // Only logs the message if the wasm debug level is equal or higher than this message level
    if( level <= debugWasmLevel )
    {
        // Print a preamble with location and number of bytes
        va_list args;
        va_start(args, fmt);
        fprintf( stderr, "MEMORY DUMP(" );
        vfprintf(stderr, fmt, args);
        fprintf( stderr, ", %d bytes):\n", len );
        
        char line[16*3+2+16+1+1]; // Where the line is going to be build, format:
                                  // hex hex ... | asc asc ... \n
                                  // hex is 3 bytes for each byte
                                  // asc is 1 byte for each byte
        line[16*3+0]='|';line[16*3+1]=' '; // Fill with the divisor |
        line[sizeof(line)-2]='\n';line[sizeof(line)-1]='\0'; // Line end
        size_t idx_hex=0; // index for the hex area
        size_t idx_ascii=16*3+2; // index for the ascii area
        
        // Go through all bytes in the buffer
        for( size_t cnt=0; cnt<len; cnt++)
        {
            // Get a byte from the buffer and convert it to hex
            // Nibble less than 0xa is converted to ascii by adding '0'
            // Nibble more than 0x9 is converted to ascii by adding 'A'-0xa
            unsigned char b=((unsigned char*)addr)[cnt];
            line[idx_hex++]= (b>>4)<0xa  ? (b>>4)+'0'  : (b>>4)+'A'-0xa;
            line[idx_hex++]= (b&0xf)<0xa ? (b&0xf)+'0' : (b&0xf)+'A'-0xa;
            line[idx_hex++]= ' ';

            // Printable ?
            if( b>=' ' && b<='~' )
            {
                line[idx_ascii++] = b;  // Yes, put the character
            }
            else
            {
                line[idx_ascii++] = '.'; // No, print the dot
            }

            // Time to print? 
            if((cnt+1) % 16 == 0 || cnt+1 == len)
            {
                // One full line is built?
                if( (cnt+1) % 16 == 0 )
                {
                    // Yes, prepare indexes for the next line
                    idx_hex=0;
                    idx_ascii=16*3+2;
                }
                else
                {
                    // No, it means the final line needs to be printed, fill in the
                    // missing bytes with spaces
                    while( idx_ascii<(sizeof(line)-2) )
                    {
                        line[idx_hex++]     = ' ';
                        line[idx_hex++]     = ' ';
                        line[idx_hex++]     = ' ';
                        line[idx_ascii++]   = ' ';
                    }
                }
                
                // Print the line
                fprintf( stderr, "%s", line);
            }
        }
    }
}

Placing HexDump Inside Your Webassembly Code

Use the DBG_DUMP macro. In the following example I add DBG_DUMP before and after the sha256Compute() function call, which allows me to see the function’s input data and its result:


EMSCRIPTEN_KEEPALIVE void sha256Test(void)
{
    unsigned char shaInput[3]={'a','b','c'};
    unsigned char shaOutput[256/8];
    
    // DUMP SHA256 input data
    DBG_DUMP(DEBUG_WASM_DEBUG, shaInput, sizeof(shaInput), "%s", "SHA Input");
    
    sha256Compute(shaInput, sizeof(shaInput), shaOutput);
    
    // DUMP SHA256 result
    DBG_DUMP(DEBUG_WASM_DEBUG, shaOutput, sizeof(shaOutput), "%s", "SHA Output");
}

Conclusion

This was a quick post, but it brings an important debug feature to our Webassembly debug infrastructure. It already helped me a lot in figuring out why my SHA256 implementation was not working.


Leave a message below. Webassembly is evolving rapidly, please let me know if this post got outdated.

Enjoyed this post?

Don't miss new posts: Share it with your friends:

You may also like...

1 Response

Leave a Reply

Your email address will not be published. Required fields are marked *