NASM - The Netwide Assembler
NASM Forum => Programming with NASM => Topic started by: landyS3 on April 30, 2020, 06:33:01 PM
-
Many years ago, in my 32-bit windows masm days, I managed to get a fullscreen OpenGl teapot rendered, and could zoom and rotate it using the mouse wheel and keys etc. It was all done in assembler only. But that was then. Now, having spent lots of time getting to know Linux Mint, Nasm and 64-bit assembly language programming, I wanted to get started again with OpenGL. The program below is supposed to simply open an OpenGL window and draw a triangle. It works perfectly in its C version built with gcc, but the assembly version doesn't work. It assembles with nasm without error or warning to a 3.2 kB object file triangle.o, and it links with gcc without error or warning to a 13.4 kB "unknown" file triangle. But when I try and run it using ./triangle I get loads of errors:
./triangle: Symbol `glClearColor' causes overflow in R_X86_64_PC32 relocation
...and the same for all the other function calls, then finally...
Segmentation fault (core dumped)
Does anyone know (and can explain to me) what the problem is? I'm guessing it's something to do with the build process, but to be honest I'm at my wits end. I love assembly language, especially nasm, and it would be a great pity if I had to abandon it all now in favour of doing everything in C. I'd really love to be able to do OpenGL in assembly language.
Many thanks in anticipation,
Phil
;----------------------------------------------------
; triangle.asm
; Simple triangle using opengl and freeglut
; To assemble:
; nasm -felf64 -Wall triangle.asm -o triangle.o
; To link:
; gcc triangle.o -lglut -lGL -lGLU -o triangle
; To run:
; ./triangle
;----------------------------------------------------
default rel
global main
extern glutInit
extern glutInitDisplayMode
extern glutInitWindowSize
extern glutInitWindowPosition
extern glutCreateWindow
extern glutDisplayFunc
extern glutMainLoop
extern glClearColor
extern glClear
extern glColor3f
extern glOrtho
extern glBegin
extern glVertex3f
extern glEnd
extern glFlush
;----------------------------------------------------
section .data
dqminusonepointnought dq -1.0
dqplusonepointnought dq 1.0
winhandle dq 0
ddpointfour dd 0.4
ddonepointnought dd 1.0
ddminusnoughtpointseven dd -0.7
ddplusnoughtpointseven dd 0.7
ddnoughtpointnought dd 0.0
ddminusonepointnought dd -1.0
wintitle db "Triangle",0
;----------------------------------------------------
section .text
drawtriangle:
movd xmm0, dword [ddpointfour]
movd xmm1, dword [ddpointfour]
movd xmm2, dword [ddpointfour]
movd xmm3, dword [ddpointfour]
call glClearColor
vzeroall
mov rdi, 16384
call glClear
xor rdi, rdi
movd xmm0, dword [ddonepointnought]
movd xmm1, dword [ddonepointnought]
movd xmm2, dword [ddonepointnought]
call glColor3f
vzeroall
movq xmm0, qword [dqminusonepointnought]
movq xmm1, qword [dqplusonepointnought]
movq xmm2, qword [dqminusonepointnought]
movq xmm3, qword [dqplusonepointnought]
movq xmm4, qword [dqminusonepointnought]
movq xmm5, qword [dqplusonepointnought]
call glOrtho
vzeroall
mov rdi, 4
call glBegin
xor rdi, rdi
movd xmm0, dword [ddminusnoughtpointseven]
movd xmm1, dword [ddplusnoughtpointseven]
movd xmm2, dword [ddnoughtpointnought]
call glVertex3f
vzeroall
movd xmm0, dword [ddplusnoughtpointseven]
movd xmm1, dword [ddplusnoughtpointseven]
movd xmm2, dword [ddnoughtpointnought]
call glVertex3f
vzeroall
movd xmm0, dword [ddnoughtpointnought]
movd xmm1, dword [ddminusonepointnought]
movd xmm2, dword [ddnoughtpointnought]
call glVertex3f
vzeroall
call glEnd
call glFlush
ret
main:
lea rsp,[rsp-8]
call glutInit
xor rdi, rdi
xor rsi, rsi
call glutInitDisplayMode
mov rdi, 500
mov rsi, 500
call glutInitWindowSize
xor rdi, rdi
xor rsi, rsi
mov rdi, 100
mov rsi, 100
call glutInitWindowPosition
xor rdi, rdi
xor rsi, rsi
mov rdi, wintitle
call glutCreateWindow
mov [winhandle], rax
xor rdi, rdi
xor rax, rax
mov rdi, drawtriangle
call glutDisplayFunc
xor rdi, rdi
call glutMainLoop
ret
-
Hi Phil,
Welcome to the forum.
I know little of 64-bit code and nothing of OpenGL. I probably can't help you.
As a wild asm guess... very wild... try square brackets...
call [glClearColor]
etc.
You wouldn't normally do this for a function call, but my (very) vague recollection of OpenGl os that it's weird.
I doubt it, but I hope that helps.
Best,
Frank
-
Hi Frank,
Nope, that's not it, but thanks for trying to help, and the welcome.
Regards,
Phil
-
You forgot some steps (like loading the identity matrix)...
Old style OpenGL triangle example using GLUT:
#include <GL/glut.h>
void reshape( int w, int h )
{
glViewport( 0, 0, w, h );
glMatrixMode( GL_PROJECTION );
glLoadIdentity();
glOrtho( 0, w, 0, h, -1, 1 );
glScalef( 1, -1, 1 );
glTranslatef( 0, -h, 0 );
}
void display( void )
{
glClear( GL_COLOR_BUFFER_BIT );
glBegin( GL_TRIANGLES );
glColor3f( 0.0, 0.0, 1.0 );
glVertex2i( 0, 0 );
glColor3f( 0.0, 1.0, 0.0 );
glVertex2i( 200, 200 );
glColor3f( 1.0, 0.0, 0.0 );
glVertex2i( 20, 200 );
glEnd();
glFlush();
}
int main( int argc, char **argv )
{
glutInit( &argc, argv );
glutCreateWindow( "single triangle" );
glutDisplayFunc( display );
glutReshapeFunc( reshape );
glutMainLoop();
return 0;
}
Now, compile linking witg opengl and glut libraries.
Ask your compiler to generate assembly listing -Fa in VC++ or -S in GCC and see what is happening...
-
Thanks fredericopissarra, but as I said originally I already know how to do it all in C. What I want to do is do it in 64-bit Linux assembler using Nasm. And I did look at the assembly code that C generates, but it didn't seem (to me) to shed any light on things.
Regards,
Phil
-
Thanks fredericopissarra, but as I said originally I already know how to do it all in C. What I want to do is do it in 64-bit Linux assembler using Nasm. And I did look at the assembly code that C generates, but it didn't seem (to me) to shed any light on things.
Regards,
Phil
Direct translation from simple.c to NASM simple.asm (with comments):
; simple.asm
;
; To compile and link:
;
; $ nasm -f elf64 -o simple.o simple.asm
; $ gcc -s -o simple simple.o -lGL -lglut
;
bits 64
default rel
GL_CLEAR_BUFFER_BIT equ 16384
GL_PROJECTION equ 5889
GL_TRIANGLES equ 4
section .rodata
wintitle: db 'simple triangle', 0
dbl_one: dq 1.0
dbl_minus_one: dq -1.0
flt_one: dd 1.0
flt_minus_one: dd -1.0
section .text
extern glBegin
extern glClear
extern glColor3f
extern glEnd
extern glFlush
extern glLoadIdentity
extern glMatrixMode
extern glOrtho
extern glScalef
extern glTranslatef
extern glVertex2i
extern glViewport
extern glutInit
extern glutCreateWindow
extern glutDisplayFunc
extern glutMainLoop
extern glutReshapeFunc
reshape:
push rbp ; SysV ABI demans savind these.
push rbx
mov ebp, edi ; save for use in glOrtho call.
mov ebx, esi
; glViewport( 0, 0, w, h )
mov ecx, esi
mov edx, edi
xor esi, esi
xor edi, edi
call glViewport wrt ..plt
; glMatrixMude( GL_PROJECTION );
mov edi, GL_PROJECTION
call glMatrixMode wrt ..plt
; glLoadIdentity();
call glLoadIdentity wrt ..plt
; glOrtho( 0.0, w, 0.0, h, -1.0, 1.0 );
pxor xmm0, xmm0 ; left
cvtsi2sd xmm1, ebp ; right (w)
pxor xmm2, xmm2 ; bottom
cvtsi2sd xmm3, ebx ; top (h)
movsd xmm4, qword [dbl_minus_one] ; nearval
movsd xmm5, qword [dbl_one] ; farval
call glOrtho wrt ..plt
; glScalef( 1.0f, -1.0f, 1.0f );
movss xmm2, dword [flt_one]
movss xmm1, dword [flt_minus_one]
movss xmm0, xmm2
call glScalef wrt ..plt
; Arguments for glTranslatef
pxor xmm0, xmm0
neg ebx
cvtsi2ss xmm1, ebx
pxor xmm2, xmm2
pop rbx ; Restore as per SysV ABI.
pop rbp
; glTranslatef( 0.0f, -h, 0.0f );
jmp glTranslatef wrt ..plt
display:
; WHY RSP needs to be handled here?!
sub rsp, 8
; glClear( GL_CLEAR_BUFFER_BIT );
mov edi, GL_CLEAR_BUFFER_BIT
call glClear wrt ..plt
; glBegin( GL_TRIANGLES );
mov edi, GL_TRIANGLES
call glBegin wrt ..plt
; glColor3f( 0.0f, 0.0f, 1.0f );
pxor xmm0, xmm0
pxor xmm1, xmm1
movss xmm2, dword [flt_one]
call glColor3f wrt ..plt
; glVertex2i( 0, 0 );
xor edi, edi
xor esi, esi
call glVertex2i wrt ..plt
; glColor3f( 0.0f, 1.0f, 0.0f );
pxor xmm0, xmm0
movss xmm1, dword [flt_one]
pxor xmm2, xmm2
call glColor3f wrt ..plt
; glVertex2i( 200, 200 );
mov edi, 200
mov esi, 200
call glVertex2i wrt ..plt
; glColor3f( 1.0f, 0.0f, 0.0f );
movss xmm0, dword [flt_one]
pxor xmm1, xmm1
pxor xmm2, xmm2
call glColor3f wrt ..plt
; glVertex2i( 20, 200 );
mov edi, 20
mov esi, 200
call glVertex2i wrt ..plt
; glEnd();
call glEnd wrt ..plt
add rsp, 8
; glFlush();
jmp glFlush wrt ..plt
global main
main:
sub rsp, 8 ; Needs to be here to take address of argc.
; RDI will be &argc.
mov [rsp], edi
lea rdi, [rsp]
; glutInit( &argc, argv ); // argv in RSI.
call glutInit wrt ..plt
; glutCreateWindow( "simple triangles" );
lea rdi, [wintitle]
call glutCreateWindow wrt ..plt
; glutDisplayFunc( display );
lea rdi, [display]
call glutDisplayFunc wrt ..plt
; glutReshapeFunc( reshape );
lea rdi, [reshape]
call glutReshapeFunc wrt ..plt
; glutMainLoop();
call glutMainLoop wrt ..plt
; return 0;
xor eax, eax
add rsp, 8
ret
-
WOW! MANY thanks fredericopissarra! Your code works a treat! It assembles, links and runs perfectly, without any errors or warnings, and produces a nice coloured triangle in a top-level window.
I'm going to have to study your code tomorrow (it's nearly 1:00 am here in England, and time for bed) in order to see what you did right, and what I did wrong, but at least I now know that OpenGL in 64-bit assembler on Linux is actually possible. I can't thank you enough!
Do you have any suggestions as to where my code and/or building process was in error? But no worries, I may figure it out eventually, but any guidance you could give as to where my code doesn't work would be greatly appreciated.
Many thanks, and my best regards,
Phil
-
As I told you, this is a direct translation from simple.c (or simple.s) to NASM. I did only little tweaks (like getting rid of RSP changing on reshape() function) and put somethings in order to better understanding.
As I said in the code, I still don't know WHY RSP must be changed in display() function (without it, you'll get a segmention fault).
Anyway, this is a direct C -> NASM translation based on GCC assembly listing.
And again: You forgot critical things in your original code, as, for example, initialize the projection matrix...
-
Have now re-written my original program and got it working properly.
;-------------------------------------------------------------------------------------
; triangle.asm
; Simple triangle using opengl and freeglut
; To assemble:
; nasm -felf64 -Wall triangle.asm -o triangle.o
; To link:
; gcc -march=native -m64 -O3 -Wall -Wextra triangle.o -lGL -lglut -o triangle
; To run:
; ./triangle
;-------------------------------------------------------------------------------------
; Note that the parameter -m64 may not suit all architectures (see -m in man gcc)
; Nasm supports elf format position-independent code (PIC) features. To call an
; external routine (library function) simply append " wrt ..plt" to the call,
; for example call glClear wrt ..plt
; (Nasm manual section 9.2)
;-------------------------------------------------------------------------------------
default rel
global main
extern glutInit
extern glutInitDisplayMode
extern glutInitWindowSize
extern glutInitWindowPosition
extern glutCreateWindow
extern glutDisplayFunc
extern glutMainLoop
extern glClearColor
extern glClear
extern glColor3f
extern glOrtho
extern glBegin
extern glVertex3f
extern glEnd
extern glFlush
;----------------------------------------------------
section .data
dq_minusone dq -1.0
dq_one dq 1.0
winhandle dq 0
dd_pointfour dd 0.4
dd_one dd 1.0
dd_minuspointseven dd -0.7
dd_pluspointseven dd 0.7
dd_minusone dd -1.0
wintitle db "Triangle",0
;----------------------------------------------------
section .text
drawtriangle:
push rbp
push rbx
lea rsp,[rsp-8]
movd xmm0, dword [dd_pointfour]
movd xmm1, dword [dd_pointfour]
movd xmm2, dword [dd_pointfour]
movd xmm3, dword [dd_pointfour]
call glClearColor wrt ..plt
mov rdi, 16384
call glClear wrt ..plt
movd xmm0, dword [dd_one]
movd xmm1, dword [dd_one]
movd xmm2, dword [dd_one]
call glColor3f wrt ..plt
movq xmm0, qword [dq_minusone]
movq xmm1, qword [dq_one]
movq xmm2, qword [dq_minusone]
movq xmm3, qword [dq_one]
movq xmm4, qword [dq_minusone]
movq xmm5, qword [dq_one]
call glOrtho wrt ..plt
mov rdi, 4
call glBegin wrt ..plt
vzeroall
movd xmm0, dword [dd_minuspointseven]
movd xmm1, dword [dd_pluspointseven]
; last parameter already zero, pxor xmm2, xmm2
call glVertex3f wrt ..plt
movd xmm0, dword [dd_pluspointseven]
; parameter already set, movd xmm1, dword [dd_pluspointseven]
; last parameter already zero, pxor xmm2, xmm2
call glVertex3f wrt ..plt
pxor xmm0, xmm0
movd xmm1, dword [dd_minusone]
; last parameter already zero, pxor xmm2, xmm2
call glVertex3f wrt ..plt
call glEnd wrt ..plt
call glFlush wrt ..plt
lea rsp,[rsp+8]
pop rbx
pop rbp
ret
main:
lea rsp, [rsp-8]
mov [rsp], edi
lea rdi, [rsp]
call glutInit wrt ..plt
xor rdi, rdi
call glutInitDisplayMode wrt ..plt
mov rdi, 500
mov rsi, 500
call glutInitWindowSize wrt ..plt
mov rdi, 100
mov rsi, 100
call glutInitWindowPosition wrt ..plt
mov rdi, wintitle
call glutCreateWindow wrt ..plt
mov [winhandle], rax
mov rdi, drawtriangle
call glutDisplayFunc wrt ..plt
call glutMainLoop wrt ..plt
xor rax, rax ; return 0 to OS
lea rsp,[rsp+8]
-
Works by accident (you still forgot to initialize projection matrix, for example). Here's your code with some notes.
;-------------------------------------------------------------------------------------
; triangle.asm
;
; Simple triangle using opengl and freeglut
;
; To assemble:
; nasm -felf64 -Wall triangle.asm -o triangle.o
; To link:
; gcc -m64 -Wall -Wextra triangle.o -lGL -lglut -o triangle
; To run:
; ./triangle
;-------------------------------------------------------------------------------------
; Note that the parameter -m64 may not suit all architectures (see -m in man gcc)
; Nasm supports elf format position-independent code (PIC) features. To call an
; external routine (library function) simply append " wrt ..plt" to the call,
; for example call glClear wrt ..plt
; (Nasm manual section 9.2)
;-------------------------------------------------------------------------------------
; Fred's notes:
;
; See FIXME notes on the code.
; Also, using -march=native and -O3 for linking is superfluous.
; If you are using gcc for x86-64, -m64 is also superfluous.
; Another one: GCC and LD documentation for -l options says it is prudent to use them
; at the END of the command, like:
;
; gcc triangle.o -o triangle -lGL -lglut
;
; Use -s options if you don't want symbolic data on your executable.
;-------------------------------------------------------------------------------------
; FIXME: Not an error, but to make sure we are dealing with x86-64 here, use
; "bits 64" directive.
default rel
global main
extern glutInit
extern glutInitDisplayMode
extern glutInitWindowSize
extern glutInitWindowPosition
extern glutCreateWindow
extern glutDisplayFunc
extern glutMainLoop
extern glClearColor
extern glClear
extern glColor3f
extern glOrtho
extern glBegin
extern glVertex3f
extern glEnd
extern glFlush
;----------------------------------------------------
; FIXME: Not an error, but since you won't change any of these values,
; it is practical to alocate them in .rodata section, instead of .data.
section .rodata
dq_minusone dq -1.0
dq_one dq 1.0
dd_pointfour dd 0.4
dd_one dd 1.0
dd_minuspointseven dd -0.7
dd_pluspointseven dd 0.7
dd_minusone dd -1.0
wintitle db "Triangle",0
; FIX: winhandle moved to .bss section because we don't need this on the executable image.
section .bss
winhandle resq 1
;----------------------------------------------------
section .text
drawtriangle:
push rbp
push rbx
; FIXME: sub rsp,8 is shorter
lea rsp,[rsp-8]
; FIXME you only need to do this ONCE.
movd xmm0, dword [dd_pointfour]
movd xmm1, dword [dd_pointfour]
movd xmm2, dword [dd_pointfour]
movd xmm3, dword [dd_pointfour]
call glClearColor wrt ..plt
; FIXME: No need to assign value to RDI, EDI will suffice.
; Assigning to EDI will zero upper 32 bits automatically
; and create a shorter instruction encoding.
mov rdi, 16384
call glClear wrt ..plt
; FIXME: This should be inside glBegin/glEnd block.
movd xmm0, dword [dd_one]
movd xmm1, dword [dd_one]
movd xmm2, dword [dd_one]
call glColor3f wrt ..plt
; FIXME: This should be done only ONCE.
; And the projection matrix should be initialized ONCE too.
movq xmm0, qword [dq_minusone]
movq xmm1, qword [dq_one]
movq xmm2, qword [dq_minusone]
movq xmm3, qword [dq_one]
movq xmm4, qword [dq_minusone]
movq xmm5, qword [dq_one]
call glOrtho wrt ..plt
; FIXME: No need to assign value to RDI, EDI will suffice.
; Assigning to EDI will zero upper 32 bits automatically
; and create a shorter instruction encoding.
mov rdi, 4
call glBegin wrt ..plt
; FIXME: No need to zero all YMM registers! pxor xmm2,xmm2 will suffice (and it is faster).
vzeroall
movd xmm0, dword [dd_minuspointseven]
movd xmm1, dword [dd_pluspointseven]
; last parameter already zero, pxor xmm2, xmm2
; FIXME: Argumentos already set, but they could be changed by the called function!
call glVertex3f wrt ..plt
movd xmm0, dword [dd_pluspointseven]
; parameter already set, movd xmm1, dword [dd_pluspointseven]
; last parameter already zero, pxor xmm2, xmm2
; FIXME: Argumentos already set, but they could be changed by the called function!
call glVertex3f wrt ..plt
pxor xmm0, xmm0
movd xmm1, dword [dd_minusone]
; last parameter already zero, pxor xmm2, xmm2
; FIXME: Argumentos already set, but they could be changed by the called function!
call glVertex3f wrt ..plt
call glEnd wrt ..plt
call glFlush wrt ..plt
; FIXME: add rsp,8 is shorter.
lea rsp,[rsp+8]
pop rbx
pop rbp
ret
main:
; FIXME: sub rsp,8 is shorter.
lea rsp, [rsp-8]
mov [rsp], edi
lea rdi, [rsp]
call glutInit wrt ..plt
; FIXME: No need to assign value to RDI, EDI will suffice.
; Assigning to EDI will zero upper 32 bits automatically
; and create a shorter instruction encoding.
xor rdi, rdi
call glutInitDisplayMode wrt ..plt
; FIXME: No need to assign value to RDI and RSI, EDI and ESI will suffice.
; Assigning to EDI and ESI will zero upper 32 bits automatically
; and create a shorter instruction encoding.
mov rdi, 500
mov rsi, 500
call glutInitWindowSize wrt ..plt
; FIXME: No need to assign value to RDI and RSI, EDI and ESI will suffice.
; Assigning to EDI and ESI will zero upper 32 bits automatically
; and create a shorter instruction encoding.
mov rdi, 100
mov rsi, 100
call glutInitWindowPosition wrt ..plt
mov rdi, wintitle
call glutCreateWindow wrt ..plt
; FIXME: glutCreateWindow will return in 'int' (DWORD).
; So you need to store only EAX. But, since you aren't
; using this handle for nothing, no storage is necessary.
mov [winhandle], rax
mov rdi, drawtriangle
call glutDisplayFunc wrt ..plt
call glutMainLoop wrt ..plt
; FIXME: No need to assign value to RAX, EAX will suffice.
; Assigning to EAX will zero upper 32 bits automatically
; and create a shorter instruction encoding.
xor rax, rax ; return 0 to OS
; FIXME: add rsp,8 will be shorter.
lea rsp,[rsp+8]
; FIX: You forgot this!
ret
-
"Works by accident". Of course, how else would a program of mine work!
"you still forgot to initialize projection matrix". What makes you think I want one. I know you want one, but I don't.
Other points...
-march=native is not superfluous -- read the gcc information
-O3 is not superfluous -- read the gcc information
-m64 possibly "might" be superflous (maybe), but it's still better to be explicit
My placement of the linked files (just before the output file specification) is fine.
Use of -s option. Agreed.
"but to make sure we are dealing with x86-64 here, use "bits 64" directive". WRONG -- You don't need "bits 64" if you are using -felf64. Read the Nasm manual.
.rodata. Possibly, but program far from optimised yet. Simply want it to function initially.
winhandle in .bss. Not a big deal in such a small program. And program far from optimised yet. Simply want it to function initially.
sub rsp,8 is shorter. Maybe, but lea is faster, and is recommended for default rel -- read the manual.
you only need to do this ONCE. Yes, that's why I have only done it once!
32-bit vs 64-bit. A minor point. Program has not been optimised yet.
This should be inside glBegin/glEnd block. Then you'd better get onto the designers of the C OpenGL tutorial and tell them they're wrong. I've put it where they put it.
This should be done only ONCE. Yes, that's why I have only done it once!
And the projection matrix should be initialized ONCE too. Yes, that's why I have only done it once!
vzeroall vs pxor. A very minor point. Program has not been optimised yet.
"glutCreateWindow will return in 'int'", should read "glutCreateWindow will return an 'int'"
"since you aren't using this handle for nothing" should read "since you aren't using this handle for anything"
I know I'm not using the window handle, so I don't need a variable to store it in, and I can just ignore the return value. But I choose to store it.
The final ret is superfluous in this program. It doesn't make any difference to the program execution.
The program might not include things that you might want to see (like perspective correction etc.), but then it's just an UN-OPTIMISED asm version of a very simple C OpenGL tutorial aimed at establishing whether OpenGL is reasonably easy to program in assembler (it's not), practical to program in assembler (it's not) and worth all the effort (probably not). Now I know.
-
Ok... I already regret trying to help... forget about it and good luck in future projects...
-
Well Thank You for helping anyway!
Best,
Frank