Lets say I have a buffer I declared like this:
.section .bss
.lcomm buffer, 33
I pushed it to the stack and passed it as an argument to another function that expects a 44-byte length buffer.
Is there a way on the called function to check if the received buffer is 44 bytes in length? Because you cannot iterate over it and check if the current byte is equal to $0
in case the buffer is empty, because then all of its bytes will be equal to $0
.
If you can move away from defining your buffer as "common" (.lcomm
, .comm
), and can just define the buffer as a normal object, I have an answer for you.
If you must define your buffer as a common, you only know that the minimum size of the buffer is what your current assembly source file's definition uses. There might be a way to find out the common size at link time when the linker actually allocates the memory for all the different definitions of the common, but I am unaware of one.
Anyway, back to solving this without using common objects.
We first change our buffer definition from a "common" to a normal object:
.section .bss
buffer: .skip 33
We can then add two lines defining the symbol buffer_end
pointing to the byte just one after the end of buffer
, and the symbol buffer_size
as the size of the buffer
in bytes:
.section .bss
buffer: .skip 33
buffer_end:
.set buffer_size, buffer_end - buffer
You can now use the buffer_end
and buffer_size
symbols in many ways like any other symbol, e.g. for passing on the buffer size or to compare an iteration pointer to buffer_end
.
You can see the symbol value in the symbol list e.g. with nm
or objdump --syms
:
0000000000000021 l *ABS* 0000000000000000 buffer_size
0000000000404078 l .bss 0000000000000000 buffer
0000000000404099 l .bss 0000000000000000 buffer_end
Note that the size 0000000000000000
is still wrong here, we will fix that later.
You can use these symbols from C code or from assembly code for runtime checks by adding a buffer size parameter to every function which accepts the buffer pointer parameter. That is the direct answer to your question, provided you do not really need a "common" object.
You can use the buffer_size
symbol for a compile time check inside the assembly source file by adding
.if buffer_size != 44
.error "buffer_size value is incorrect"
.endif
You can use the buffer_size
symbol for link time checks via a linker script, e.g. -T check-buffer-size.x
or -Wl,-T,check-buffer-size.x
:
/* check-buffer-size.x - link time check the buffer_size value */
SECTIONS {
}
INSERT AFTER .bss ;
ASSERT( (buffer_size == 44), "buffer_size is not the required 44" );
This gives you a link time assertion which, while often useful, appears to be such an unusual idea that programming languages such as C only give you a choice between compile time assertions and runtime assertions but no link time assertions.
You can even make the object have the proper type and size in the symbol table:
.section .bss
.type buffer, @object
buffer: .skip 33
buffer_end:
.set buffer_size, buffer_end - buffer
.size buffer, buffer_size
which will make the buffer appear properly in the symbol list:
0000000000000021 l *ABS* 0000000000000000 buffer_size
0000000000404078 l .bss 0000000000000021 buffer
0000000000404099 l .bss 0000000000000000 buffer_end
If you want to use any of those symbols from other compilation units (e.g. other *.S
or *.c
source files), you can declare those symbols as global:
.global buffer
.global buffer_end
.global buffer_size
and then use them e.g. from assembly with
movq buffer, %rdi
loop: /* do_something with memory at (%rdi) */
add %4, %rdi
cmp %rdi, buffer_end
jne loop
or from C with
#include <stdint.h>
extern uint32_t buffer[];
extern uint32_t buffer_end[];
extern char buffer_size[];
extern const uintptr_t buffer_size_in_bytes = (const uintptr_t) &buffer_size;
void foo(void) {
for (uint32_t *p=buffer; p<buffer_end; ++p) {
handle_buffer_element(*p);
}
}
void bar(void) {
const uintptr_t buffer_size_in_elements = buffer_size_in_bytes / 4;
for (uintptr_t i=0; i<buffer_size_in_elements; ++i) {
handle_buffer_element(buffer[i]);
}
}