Products > Programming

Deciphering some RISC-V assembly code

(1/3) > >>

I'm trying to decipher the following RISC-V assembly code:

--- Code: ---000003d0 <verify_crc>:
     3d0:        cd1ff2ef          jal t0,a0 <__riscv_save_0>
     3d4:        6405                lui s0,0x1
     3d6:        87aa                mv a5,a0
     3d8:        1171                add sp,sp,-4
     3da:        84ae                mv s1,a1
     3dc:        1471                add s0,s0,-4 # ffc <main+0x190>
     3de:        557d                li a0,-1
     3e0:    /-> 4581                li a1,0
     3e2:    |   c03e                sw a5,0(sp)
     3e4:    |   147d                add s0,s0,-1
     3e6:    |   37e9                jal 3b0 <crc32_update>
     3e8:    |   4782                lw a5,0(sp)
     3ea:    \-- f87d                bnez s0,3e0 <verify_crc+0x10>
     3ec:        fff54513          not a0,a0
     3f0: /----- c399                beqz a5,3f6 <verify_crc+0x26>
     3f2: |      0007a023          sw zero,0(a5)
     3f6: \--/-X c091                beqz s1,3fa <verify_crc+0x2a>
     3f8:    |   c088                sw a0,0(s1)
     3fa:    \-> 00153513          seqz a0,a0
     3fe:        0111                add sp,sp,4
     400:        b16d                j aa <__riscv_restore_0>

--- End code ---

Further to my discovery in another thread of a technique whereby I can specify a static constant is assigned to its own custom output section, and then the contents of that section updated with that of an arbitrary external file post-build (using objcopy), I decided to employ this technique on another project where I previously had a 4KB blob included as an array in my C source.

I have some code that runs at startup which verifies that this blob's data is not corrupt, by checking a CRC32 checksum. That function's disassembled code is above, and the corresponding C code is as follows:

--- Code: ---static const uint8_t data[4096] __attribute__((aligned(64), section(".my_data"), used, retain));

bool verify_crc(uint32_t *crc_expect, uint32_t *crc_calc) {
uint32_t crc_data, crc_new;

// Read the CRC embedded in the last 4 bytes of the data.
crc_data = *(uint32_t *)&data[(sizeof(data) / sizeof(data[0])) - sizeof(uint32_t)];

// Calculate CRC for all of the data except the last 4 bytes.
crc_new = crc32_init(); // Actually a macro that resolves to 0xFFFFFFFF
for(size_t i = 0; i < ((sizeof(data) / sizeof(data[0])) - sizeof(uint32_t)); i++) {
crc_new = crc32_update(crc_new, data[i]);
crc_new = crc32_final(crc_new); // Another macro that resolves to arg ^ 0xFFFFFFFF

// Output the expected and calculated CRCs if pointers given.
if(crc_expect != NULL) *crc_expect = crc_data;
if(crc_calc != NULL) *crc_calc = crc_new;

return (crc_new == crc_data);

--- End code ---

The problem is that I am not sure the assembly code is actually doing what it's supposed to any more after this linker section change. It seems to me via my RISC-V-noob eyes that the assembly code will never calculate the CRC32 correctly, because it appears to just call crc32_update() with a fixed second argument of zero ("li a1,0") on every iteration of the loop! Is this correct? If this is true, I guess the compiler is assuming that the array is full of zeroes because it doesn't have any initialisation values.

How can I fix this? How can I tell the compiler not to assume that the data array contains any specific values? I guess I could slap volatile on the declaration, but that somehow seems wrong to me for static const...


--- Code: ---static const uint8_t data[4096] __attribute__((aligned(64), section(".my_data"), used, retain));

--- End code ---

The static instructs the compiler that the name data is not to be exported from this compilation unit, and this a different variable than any variable called data in some other compilation unit.

Therefore, yes, the compiler knows that it contains all 0s.

Nothing at all to do with RISC-V, that's just C.

Yes, the only way is to qualify it volatile. There is no link between static and volatile, as Bruce explained.
There is no clash between const and volatile either, although that one may look less obvious. const only instructs the compiler that your code can't modify the content of the array. volatile instructs it not to assume anything about what could happen to it outside of your code, and thus will emit code that will access it everywhere you explicitely access it, no matter what.

To be even more specific: it’s not even “inaccessibility” of the variable, which makes it be “seen” as zeros. It is because that form of a declaration makes a request to fill it with zeros:(1)
--- Quote ---(…) If an object that has static or thread storage duration is not initialized explicitly, then:
— if it has pointer type, it is initialized to a null pointer;
— if it has arithmetic type, it is initialized to (positive or unsigned) zero;
— if it is an aggregate, every member is initialized (recursively) according to these rules, and any padding is initialized to zero bits;
— if it is a union, the first named member is initialized (recursively) according to these rules, and any padding is initialized to zero bits;
--- End quote ---

The language has no idea about the linker or any postprocessing you put the program through. The compiler assumes exactly what is being written. And from its perspective it’s written: generate an array of 4096 octets with unsigned 2’s complement representation, set them all to 0, then calculate CRC32 from them.

Aside from what SiliconWizard said, you can also make it non-static. After all, it is kind of visible to the outside, if you modify it during linking.

(1) 9899:2011 6.7.9§10, repeated in working draft of C2x.

What I generally do here is to declare the symbol extern, then define it in an assembly file or just exporting a symbol directly from the linker script.  This ensures that C initialization rules do not apply.


[0] Message Index

[#] Next page

There was an error while thanking
Go to full version
Powered by SMFPacks Advanced Attachments Uploader Mod