Допустим, у меня есть буфер, который я объявил следующим образом:
.section .bss
.lcomm buffer, 33
Я поместил его в стек и передал в качестве аргумента другой функции, которая ожидает буфер длиной 44 байта.
Есть ли способ вызываемой функции проверить, имеет ли длина полученного буфера 44 байта? Потому что вы не можете перебирать его и проверять, равен ли текущий байт $0
, если буфер пуст, потому что тогда все его байты будут равны $0
.
Если вы можете отказаться от определения своего буфера как «общего» (.lcomm
, .comm
) и просто определить буфер как обычный объект, у меня есть для вас ответ.
Если вам необходимо определить свой буфер как общий, вы знаете только, что минимальный размер буфера — это тот, который использует определение текущего исходного файла сборки. Возможно, существует способ узнать общий размер во время компоновки, когда компоновщик фактически выделяет память для всех различных определений общего, но я об этом не знаю.
В любом случае, вернемся к решению этой проблемы без использования общих объектов.
Сначала мы меняем определение нашего буфера с «общего» на обычный объект:
.section .bss
buffer: .skip 33
Затем мы можем добавить две строки, определяющие символ buffer_end
, указывающий на байт сразу после конца buffer
, и символ buffer_size
как размер buffer
в байтах:
.section .bss
buffer: .skip 33
buffer_end:
.set buffer_size, buffer_end - buffer
Теперь вы можете использовать символы buffer_end
и buffer_size
разными способами, как и любой другой символ, например. для передачи размера буфера или для сравнения указателя итерации с buffer_end
.
Вы можете увидеть значение символа в списке символов, например. с nm
или objdump --syms
:
0000000000000021 l *ABS* 0000000000000000 buffer_size
0000000000404078 l .bss 0000000000000000 buffer
0000000000404099 l .bss 0000000000000000 buffer_end
Обратите внимание, что размер 0000000000000000
здесь по-прежнему неправильный, мы исправим это позже.
Вы можете использовать эти символы из кода C или ассемблерного кода для проверок во время выполнения, добавляя параметр размера буфера к каждой функции, которая принимает параметр указателя буфера. Это прямой ответ на ваш вопрос, если вам действительно не нужен «общий» объект.
Вы можете использовать символ buffer_size
для проверки времени компиляции внутри исходного файла сборки, добавив
.if buffer_size != 44
.error "buffer_size value is incorrect"
.endif
Вы можете использовать символ buffer_size
для проверки времени компоновки с помощью скрипта компоновщика, например. -T check-buffer-size.x
или -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" );
Это дает вам утверждение времени компоновки, которое, хотя часто и полезно, кажется настолько необычной идеей, что языки программирования, такие как C, дают вам только выбор между утверждениями времени компиляции и утверждениями времени выполнения, но не утверждениями времени компоновки.
Вы даже можете указать объекту правильный тип и размер в таблице символов:
.section .bss
.type buffer, @object
buffer: .skip 33
buffer_end:
.set buffer_size, buffer_end - buffer
.size buffer, buffer_size
благодаря чему буфер будет правильно отображаться в списке символов:
0000000000000021 l *ABS* 0000000000000000 buffer_size
0000000000404078 l .bss 0000000000000021 buffer
0000000000404099 l .bss 0000000000000000 buffer_end
Если вы хотите использовать любой из этих символов из других модулей компиляции (например, других исходных файлов *.S
или *.c
), вы можете объявить эти символы как глобальные:
.global buffer
.global buffer_end
.global buffer_size
а затем использовать их, например. от сборки с
movq buffer, %rdi
loop: /* do_something with memory at (%rdi) */
add %4, %rdi
cmp %rdi, buffer_end
jne loop
или из C с помощью
#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]);
}
}
Большое спасибо за ваш ответ! это здорово, я многому научился благодаря этому!
@user16713791 user16713791 Мне пришлось полностью переписать ответ, чтобы избавиться от «общего» типа объекта (.comm
или .lcomm
), потому что «общие» объекты размещаются компоновщиком, и очень сложно определить метки для начала и конца " общие» объекты, созданные компоновщиком. Мне следовало протестировать и убедиться, что мое необычное решение также работает, прежде чем публиковать его. Извините, что вызвал путаницу. Мне кажется, что отказ от использования «common» — единственный способ, но у кого-то с большим опытом сборки все равно может быть решение с «common» объектами.
Если вы передаете адрес буфера функции, то единственная передаваемая информация — это адрес буфера — где может храниться что-то еще? Если вы хотите проверить длину, вам придется либо передать ее как отдельный параметр, либо потребовать, чтобы длина сохранялась в начале буфера (а затем сохранялась после длины).