Is there a way to use math expressions in gnu assembly constants?
Asked Answered
W

1

6

What is the correct gnu assembly syntax for doing the following:

.section .data2
.asciz "******* Output Data ********"
total_sectors_written:   .word 0x0
max_buffer_sectors: .word ((0x9fc00 - $data_buffer) / 512)  # <=== need help here
.align 512
data_buffer: .asciz "<The actual data will overwrite this>"

Specifically, I'm writing a toy OS. The code above is in 16-bit real mode. I'm setting up a data buffer that will be dumped back to the boot disk. I want to calculate the number of sectors there are between where data_buffer gets placed in memory, and the upper bound of that data buffer. (Address 0x9fc00 is where the buffer would run into RAM reserved for other purposes.)

I know I could write assembly code to calculate this; but, since it is a constant known at build time, I'm curious if I can get the assembler to calculate it for me.

I'm running into three specific problems:

(1) If I use $data_buffer I get this error:

os_src/boot.S: Assembler messages:
os_src/boot.S:497: Error: missing ')'
os_src/boot.S:497: Error: can't resolve `L0' {*ABS* section} - `$data_buffer' {*UND* section}

which I find confusing, because I should use $ when I want the memory address of a label, correct?

(2) If I use data_buffer instead of $data_buffer, I get this error:

os_src/boot.S: Assembler messages:
os_src/boot.S:497: Error: missing ')'
os_src/boot.S:497: Error: value of 653855 too large for field of 2 bytes at 31
make: *** [obj/boot/dd_test.o] Error 1

which seems to suggest that the assembler is complaining about the size of the intermediate value (which does not need to fit in a 16-bit word).

(3) And, of course, what's up with the missing ')'?

Wilda answered 3/5, 2017 at 17:27 Comment(8)
You should not use $. Even so, you are limited in what you can do. Also note using multiple sections and gas in general for 16 bit code is not a good idea. PS: my assembler (v2.22) complains that invalid operands (*ABS* and .data2 sections) for '-' and that at least makes sense.October
Why is the $ inappropriate in this case? (I figured out that it was by trial-and-error, but am still confused when to use $ and when not to.) (See also #43593184)Wilda
My toy OS starts in real mode, switches to protected mode to run "regular" 32-bit code, then returns to real mode at the very end to dump the memory buffer back to disk. If gas isn't the right tool for the 16-bit code, what is a good alternative? What about using nasm to compile the 16-bit code and gcc to compile the rest and link it altogether?Wilda
$ is a signal for an immediate operand in an instruction only, it tells the assembler to emit an immediate and not an effective address. Also it applies to the whole operand, not the symbol, e.g. movl $4+foo, %eax is legal.October
Yes, you can use nasm, but in general you don't want to link the 16 and 32 bit code. Let nasm produce flat binary output and use your build system to construct the image as appropriate. Transitions usually happen through fixed addresses.October
As far as I understand, the linker is the build system: It is what is producing the raw, bootable .img file.Wilda
I don't recommend using the linker for such purpose, linker scripts (that you probably need) add unnecessary complexity.October
Let us continue this discussion in chat.Wilda
B
8

When you use expressions in GNU assembler they have to resolve to absolute values. GNU assembler isn't aware of what the origin point of the code will actually be at. That is what the linker is for. Because of that data_buffer absolute address isn't known until linking is done so it is considered relocatable. If you take an absolute value like 0x9fc00 and subtract a relocatable value from it you get a relocatable value. Relocatable values can't be used in constant (absolute) expressions.

All is not lost. The linker itself will know the absolute address once it arranges everything in memory. You seem to suggest you already use a linker script which means the work you have to do is minimal. You can use the linker to compute the value of max_buffer_sectors.

Your linker script will have a SECTIONS directive like:

SECTIONS
{
    [your section contents here]
}

You can create a linker symbol max_buffer_sectors with something like:

SECTIONS
{
    max_buffer_sectors = (0x9fc00 - (data_buffer)) / 512;
    [your section contents here]
}

This will allow the linker to compute the size since it will know data_buffer absolute address in memory.

Your GNU assembly file will need a bit of tweaking:

.globl data_buffer

.section .data2
.asciz "******* Output Data ********"
total_sectors_written:   .word 0x0
.align 512
data_buffer: .asciz "<The actual data will overwrite this>"

You'll notice I used .globl data_buffer. This exports the symbol and makes it global so that the linker can use it.

You can then use the symbol max_buffer_sectors in code like:

mov $max_buffer_sectors, %ax
Bunion answered 3/5, 2017 at 19:33 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.