Problems with segmenting and nasm

Programming, for all ages and all languages.
Post Reply
User avatar
sdose
Posts: 9
Joined: Wed Nov 02, 2016 2:20 am

Problems with segmenting and nasm

Post by sdose »

Hi,

I have some pieces of code for dev86/bcc (mostly inline assembly), both of them are working fine. Now I'm trying to use this with nasm, but my code won't work.

Maybe some of you guys can help me.

First, the working C code:

Code: Select all

uint16_t get_mem(uint16_t seg, uint16_t off) {
	#asm
		mov bx, sp
		mov es, [bx+2]	; seg
		mov	bx, [bx+4]	; off
		seg	es
		mov	ax, [bx]
	
	
	#endasm
}

/* write a 16bit word to an absolute location in SEG:OFF format */
void set_mem(uint16_t seg, uint16_t off, uint16_t data) {
	#asm
		mov bx, sp
		mov es, [bx+2]	; seg
		mov ax, [bx+6]	; data
		mov bx, [bx+4]	; off
		seg es
		mov [bx], ax
	#endasm
}
And here's the not-working code for nasm:

Code: Select all

; get a 16bit word from an absolute location in SEG:OFF format
; takes SEG:OFF in DX:AX
; returns data in  AX
get_mem:
	pusha
	mov bx, ax	; off to bx
	mov es, dx
	mov ax, [es:bx]
	popa
	ret

; takes SEG:OFF in DX:AX
; takes data in    BX
set_mem:
	pusha
	mov bx, ax	; off to bx
	mov es, dx
	mov [es:bx], ax 
	popa
	ret
Here's how I am testing it:
There should be a word (0x3456) written at 0x0570. After reading the same memory location, function print_hex16 (input in AX) prints 0x0070, the value that was written to AX before the call to get_mem.

Code: Select all

	mov dx, 0x0050
	mov ax, 0x0070
	mov bx, 0x3456
	call set_mem

	mov dx, 0x0050
	mov ax, 0x0070
	call get_mem
	
	mov bx, 0x07	; default attribute
	call print_hex16
Thanks for reading and maybe answering...
sdose
Octocontrabass
Member
Member
Posts: 5513
Joined: Mon Mar 25, 2013 7:01 pm

Re: Problems with segmenting and nasm

Post by Octocontrabass »

Your get_mem function doesn't work because PUSHA and POPA save and restore AX, which means your return value gets overwritten by POPA. You're also not saving/restoring ES, which will affect string instructions.

Your set_mem function doesn't work because you overwrite the value you're going to write to memory with the offset.
User avatar
JAAman
Member
Member
Posts: 879
Joined: Wed Oct 27, 2004 11:00 pm
Location: WA

Re: Problems with segmenting and nasm

Post by JAAman »

the most obvious problem I see is that pusha/popa surrounding the function... do you understand what those instructions do?

if you examine the function and think about what those functions do, it should be obvious what your problem is


however, both your functions fail to return ES to its previous state (perhaps that is intentional, but in case its not, be aware of it -- note your compiler will likely assume that ES was unmodified)

also the C version only works if DS.base==SS.base... while this is true for most PMode C platforms, this could be a problem (this can be fixed either by removing the mov bx,sp line and using sp offsets in the rest of the lines, or (more traditionally) changing it to mov bp, sp, and changing the remaining offsets to bp rather than bx)
User avatar
sdose
Posts: 9
Joined: Wed Nov 02, 2016 2:20 am

Re: Problems with segmenting and nasm

Post by sdose »

Oh thank you guys!

I know what these do, but it seems I was confused with pushing and popping, saving context...

The C code was written for 16bit real mode .com-files with a single segment for all. So i assume it is correct, what i have there...

Now it is working:

Code: Select all

; get a 16bit word from an absolute location in SEG:OFF format
; takes SEG:OFF in DX:AX
; returns data in  AX
get_mem:
	push es
	mov bx, ax		; off to bx
	mov es, dx		; segment 
	mov ax, [es:bx]
	pop es
	ret

; takes SEG:OFF in DX:AX
; takes data in    BX
set_mem:
	push es
	push bx			; save data
	mov bx, ax		; off to bx
	mov es, dx		; segment
	pop ax			; get back data
	mov [es:bx], ax 
	pop es
	ret
Octocontrabass
Member
Member
Posts: 5513
Joined: Mon Mar 25, 2013 7:01 pm

Re: Problems with segmenting and nasm

Post by Octocontrabass »

Now both of your functions clobber BX. I don't know what calling conventions you're using, but if that's a problem, you'll need to change it.

Is there any reason you didn't use xchg to swap the contents of AX and BX in your set_mem function?
User avatar
sdose
Posts: 9
Joined: Wed Nov 02, 2016 2:20 am

Re: Problems with segmenting and nasm

Post by sdose »

I'm doing this in assembler only, so there are no calling conventions i have to use.

There is no particular reason for not using xchg. I simply didn't know i about it, when i wrote this first time.

Is it already available on 80186? Is there any list, that shows which generation introduced which opcode?
Octocontrabass
Member
Member
Posts: 5513
Joined: Mon Mar 25, 2013 7:01 pm

Re: Problems with segmenting and nasm

Post by Octocontrabass »

sdose wrote:I'm doing this in assembler only, so there are no calling conventions i have to use.
In that case, you don't have to push/pop ES either, unless you really want to.
sdose wrote:Is it already available on 80186? Is there any list, that shows which generation introduced which opcode?
It's available on all x86 CPUs, including the 8086 and 8088. There are some nice lists here that include information on the minimum CPU requirements for each instruction.
User avatar
Brendan
Member
Member
Posts: 8561
Joined: Sat Jan 15, 2005 12:00 am
Location: At his keyboard!
Contact:

Re: Problems with segmenting and nasm

Post by Brendan »

Hi,
sdose wrote:Now it is working:
That is unfortunate - the code is so inefficient that it would be better if it didn't work.

In general; when something is mostly just a single instruction it should never be a function/routine; because a function/routine adds the overhead of a call/return plus the overhead of moving things around (to get input and output parameters in the expected places) plus the overhead of saving/restoring registers; and because a function/routine makes it harder to maintain the code (e.g. having to try to remember if "mov dx,0x50" is for the "segment parameter" or for the "offset parameter" or for something else); and because a function/routine makes it impossible to optimise the code (e.g. when 2 function calls end up using the same segment it makes it impossible to remove the unnecessary second segment load).

Consider something a little more like this:

Code: Select all

	mov ax,0x0050
	mov fs,ax

	mov word [fs:0x0070],0x3456

	mov ax,[fs:0x0070]
	
	mov bx, 0x07	; default attribute
	call print_hex16

Cheers,

Brendan
For all things; perfection is, and will always remain, impossible to achieve in practice. However; by striving for perfection we create things that are as perfect as practically possible. Let the pursuit of perfection be our guide.
User avatar
sdose
Posts: 9
Joined: Wed Nov 02, 2016 2:20 am

Re: Problems with segmenting and nasm

Post by sdose »

Brendan wrote: In general; when something is mostly just a single instruction it should never be a function/routine;
I've seperated it, because i need this function in a few places and it still is much easier for me to understand my assembly code, if i have everything seperated and named.
because a function/routine adds the overhead of a call/return plus the overhead of moving things around (to get input and output parameters in the expected places) plus the overhead of saving/restoring registers;
I need this one in first place for accessing the IVT on boot, so it is not critical as it comes to speed.
and because a function/routine makes it harder to maintain the code (e.g. having to try to remember if "mov dx,0x50" is for the "segment parameter" or for the "offset parameter" or for something else);
As far as i can tell, it looks like DX:AX is a semi-standard on giving address in SEG:OFF format and for 32bit data. I'm using this in all my functions, that are working with SEG:OFF or 32bit data.

Having xchg in mind and your fs-driven solution, I think i can optimize it a bit, but i really want to keep it in seperated functions.
As it will be used in different places, i think it is quite handy, regardless the speed-loss of context saving/restoring. I don't have to take care of the context, and always the same function used for the (more or less) same thing.
User avatar
Brendan
Member
Member
Posts: 8561
Joined: Sat Jan 15, 2005 12:00 am
Location: At his keyboard!
Contact:

Re: Problems with segmenting and nasm

Post by Brendan »

Hi,
sdose wrote:
Brendan wrote: In general; when something is mostly just a single instruction it should never be a function/routine;
I've seperated it, because i need this function in a few places and it still is much easier for me to understand my assembly code, if i have everything seperated and named.
In that case, why not write functions to avoid using all of the other instructions too? That way, instead of doing something simple like (e.g) "add ax,bx" you could turn it into a bloated and unmaintainable mess in the hope of avoiding becoming familiar with assembly language programming... :roll:
sdose wrote:
because a function/routine adds the overhead of a call/return plus the overhead of moving things around (to get input and output parameters in the expected places) plus the overhead of saving/restoring registers;
I need this one in first place for accessing the IVT on boot, so it is not critical as it comes to speed.
For boot code; I'd recommend setting all segment registers (CS, SS, DS and ES) to 0x0000 and leaving them like that unless you have to access something outside the first 64 KiB; partly because it makes things so much easier (especially if/when you're switching to/from protected mode to copy data from disk to above 0x000100000, access ACPI tables, clear video card's linear frame buffer, etc).
sdose wrote:
and because a function/routine makes it harder to maintain the code (e.g. having to try to remember if "mov dx,0x50" is for the "segment parameter" or for the "offset parameter" or for something else);
As far as i can tell, it looks like DX:AX is a semi-standard on giving address in SEG:OFF format and for 32bit data. I'm using this in all my functions, that are working with SEG:OFF or 32bit data.
DX:AX is relatively common for 32-bit integers in 16-bit code (and EDX:EAX for 64-bit integers in 32-bit code), mostly because that's what the CPU expects for unsigned multiplication and division (the registers used by "MUL" and "DIV" instructions). The "standard" for SEG:OFF is to use a segment register (e.g. "DS:SI" and "ES:DI" are the most common, mostly because of the CPU's string instructions).
sdose wrote:Having xchg in mind and your fs-driven solution, I think i can optimize it a bit, but i really want to keep it in seperated functions.
As it will be used in different places, i think it is quite handy, regardless the speed-loss of context saving/restoring. I don't have to take care of the context, and always the same function used for the (more or less) same thing.
As it will be used in different places (and not just for accessing the firmware's IVT); don't forget that "xchg" (when an operand is a memory access) has an implied "lock" (for multi-CPU consistency) which can make it much slower than a pair of "mov" instructions.


Cheers,

Brendan
For all things; perfection is, and will always remain, impossible to achieve in practice. However; by striving for perfection we create things that are as perfect as practically possible. Let the pursuit of perfection be our guide.
Post Reply