Search code examples
c++assemblygnu-assembler

Check distance between labels when importing binary file gnu-assembler


I have the following macro to embed binary data from filename:

#define INCBIN(identifier, filename)                                                          \
    asm(".pushsection .rodata\n"                                                                   \
        "\t.local " #identifier "_begin\n"                                                         \
        "\t.type " #identifier "_begin, @object\n"                                                 \
        "\t.align 16\n" #identifier "_begin:\n"                                                    \
        "\t.incbin \"" filename "\"\n\n"                                                           \
                                                                                                   \
        "\t.local " #identifier "_end\n"                                                           \
        "\t.type " #identifier "_end, @object\n"                                                   \
        "\t.align 1\n" #identifier "_end:\n"                                                       \
        "\t.byte 0\n"                                                                              \
        "\t.popsection\n");                                                                        \
                                                                                                   \
    extern std::byte const identifier##_begin[];                                                   \
    extern std::byte const identifier##_end[]

I would like to be able to claim that it is an uint32_t instead of an std::byte. This would require the file to have an integer multiple of 4 bytes. Alignment is already taken care of by the macro (align to 16). Can I trigger a compile-time error if the file has a bad size? That is, add some compile-time assert to the assembly.


Solution

  • If you just .p2align 2 after the file, it will round the total size up to a multiple of 4 bytes, regardless of how many bytes .incbin assembled to. (Since the file started at an aligned position).


    If you want to check instead of pad, that's possible at assemble time, using assembler directives. Obviously not at compile-time proper, since that just translates C++ to asm to later be fed to the assembler. (In GCC, those steps are actually separate, not all part of the same compiler process. But logically still sequential in other implementations.)

    The distance between two labels in the same file is an assemble-time constant that you can use in GAS expressions like (b-a)&3.

    If you use .rept -((b-a)&3), you'll have a 0 or negative repeat count, the latter of which is an assemble-time error, according to the file size % 4 being non-zero. (Fun fact: the Linux kernel uses a trick like this in C to do static asserts by generating an array with 0 or negative size).

    Or even better, .error can be controlled by .rept or other GAS directives like .ifgt. (.rept would repeat the error message (b-a)%4 times, vs. if-greater-than-zero repeating it only once.)

    .p2align 2
    a:
      .incbin "bin"
      # .p2align 2
    b:
    
    .ifgt (b-a)&3         # true if remainder > 0
      .error  "asm static_assert failed: file size not a multiple of 4"
    .endif
    
    $ echo xyz > bin   # size 4
    $ gcc -c foo.s
    $ echo >> bin      # size 5
    $ gcc -c foo.s
    foo.s: Assembler messages:
    foo.s:9: Error: asm static_assert failed: file size not a multiple of 4
    

    Tested with GNU assembler (GNU Binutils) 2.36.1

    I'll leave it up to you to stuff that back into your inline asm macro, with the filename in the .error string.