NASM - The Netwide Assembler
NASM Forum => Programming with NASM => Topic started by: smaudet on November 25, 2018, 12:47:00 AM
-
Hello all.
So I was trying to learn the NASM assembler a bit more, my end goal is to be able to try to load some library and be able to call it with arbitrary arguments, in particular problably kernel32.dll (I don't know if its even possible yet)...
Anyways, I'm faced with a much more immediate problem, I can't get Hello World to work on windows with the nasm compiler and the gcc linker. I'm using MSYS2's variant of MINGW64, and I've the following code:
; ----------------------------------------------------------------------------
; helloworld.asm
;
; This is a Win32 console program that writes "Hello, World" on one line and
; then exits. It needs to be linked with a C library.
; ----------------------------------------------------------------------------
; https://forum.nasm.us/index.php?topic=810.0 ???
global main
extern printf
section .text
main:
push message
call printf
; add esp, 4
; call ExitProcess
ret
message:
db 'Hello, World', 10, 0
I'm using the following:
nasm -f win64 -o win-nasm.obj win-nasm.asm
gcc -o win-nasm.exe win-nasm.obj
This produces a win-nasm.exe which promptly proceeds to segfault as soon as I attempt to run it.
I can get a Hello World to work from nasm but it has to be linked with golink (and it uses kernel32 directly) and it needs to be run from a command prompt. Does anyone know what I'm missing here?
-
1) You're compiling 32-bit source with NASM's "win64" switch
2) You're linking without being explicit of either "-m32" or "-m64" switch, according to #1.
3) You're linking to multiple libraries... "printf" belongs to GCC by default... "ExitProcess" belongs to "kernel32.dll". "printf" could also belong to "msvcrt.dll". Which one?
4) For GCC32 bit, you should know a thing or two about C calling convention and C general structure.
Here's a recent example... closest to what you are asking
https://forum.nasm.us/index.php?topic=2475.0
-
1) What is 32 bit about it? the only explicitly 32 bit thing I see is the line that's commented out.
2) 32 bit won't even link. I verified -fwin64 means 64 bit code, not 32 (I know windows has this weird WOWSYS64 thing which is 32 bit, I don't think that's relevant here). -m64 corresponds to -fwin64, -m32 to -fwin32
3) I'm not using ExitProcess. Its commented out...I have a ret and I did also try with push 0. I'm not sure where/what printf is, nor how to specify that.
4) Link? I don't think I'm doing anything special here, I'm putting a string pointer on the stack and then invoking the C ABI?
That recent example is only dealing with the kernel calls and MSVC.... I'm specifically trying to figure out why mingw64 won't compile.
Worth saying the 64 bit stuff compiles and segfaults, the 32 bit stuff seems to get very confused.
-
It's a 32-bit code.
push message
call printf
add esp,4
is a 32-bit CDECL or C calling convention. For 64-bit code, it is different entirely. Using "push" indicates it is a 32-bit code. And should not be compiled with "-f win64" switch. And for that, choosing the right linking switches is crucial.
extern printf
extern scanf
section .data
prmpt1: db 'Enter an integer: ',0
prmpt2: db 'Your integer is %lld',0ah,0
format: db '%lld',0
value: dq 0
section .text
global main
main:
sub rsp,40 ;stack alignment + shadow space
mov rcx,prmpt1
call printf
mov rdx,value
mov rcx,format
call scanf
mov rdx,qword[value]
mov rcx,prmpt2
call printf
add rsp,40
ret
; nasm -f win64 myprog.asm
; gcc -m64 myprog.obj -s -o myprog.exe
At this point, I am not sure what exactly you want. The whole picture you're trying to give me is that you're using 32-bit code and 32-bit calling convention, compiled into 64-bit binary format (which is PE32+ instead of PE), using 32-bit linking switch.. Is that what you're trying to do? You need to make up your mind.
EDIT: Added full 64-bit code and put them in a code tag
-
Just in case you actually need to compile a 32-bit code, use this
extern _printf
extern _scanf
section .data
prmpt1: db 'Enter an integer: ',0
prmpt2: db 'Your integer is %d',0ah,0
format: db '%d',0
value: dd 0
section .text
global _WinMain@16
_WinMain@16:
push ebp
mov ebp,esp
push prmpt1
call _printf
add esp,4*1
push value
push format
call _scanf
add esp,4*2
push dword[value]
push prmpt2
call _printf
add esp,4*2
mov esp,ebp
pop ebp
ret
; nasm -f win myprog.asm
; gcc -m32 myprog.obj -s -o myprog.exe
Calling convention makes a huge difference in the way you compile and link your code.
EDIT: I gave you a full 32-bit example and put them in a code tag.
-
Thanks for the clarification.
; ----------------------------------------------------------------------------
; win-nasm-64.asm
;
; This is a Win64 console program that writes "Hello, World" on one line and
; then exits. It needs to be linked with a C library.
; nasm -o win-nasm-64.obj -fwin64 win-nasm-64.asm && gcc -m64 win-nasm-64.obj -o win-nasm-64.exe
; ----------------------------------------------------------------------------
global main
extern printf
section .text
main:
sub rsp, 40
mov rcx, message
call printf
add rsp, 40
ret
message:
db 'Hello, World', 0
Is what I currently have.
In addition to your code, these were quite helpful to understand a bit better:
https://msdn.microsoft.com/en-us/library/zthk2dkh.aspx
https://msdn.microsoft.com/en-us/library/9z1stfyw.aspx
Regarding 'making up my mind', no, I'm just learning, hence I was just trying some simple examples and trying to establish a baseline. Good thing too, it seems it turned out the 'baseline' was much more involved than I had expected.
I seem to have 'conquered' the 64 bit example, now to try for 32 bit variants, proper makefiles, etc. :)
Thanks for your help!
-
Glad you sorted it out.
But be careful with what you read from MSDN. Some things don't quite apply to C when it comes to parameter-passing. For example, printf don't take in XMM arguments. So when it comes to C, I don't usually direct people to MS links and articles because they tend to mislead. Here, try this
format: db '%f',0
myFloat: dq 56.4067
movq xmm1,[myFloat] ;according to "parameter-passing", myFloat should go here... but...
;mov rdx,[myFloat]
mov rcx,format
call printf
Good luck with your "64-bit conquest" ;D
-
Unless I'm mistaken (quite possible!) printf wants the count of float parameters in al.
Best,
Frank
-
Not on Win64 frank. For Linux64, yes. Things deviate sharply for C on both platforms.
-
Okay, my mistake. Thanks dreamCoder!
Best,
Frank
-
Just in case you actually need to compile a 32-bit code, use this
...
_WinMain@16:
push ebp
mov ebp,esp
...
mov esp,ebp
pop ebp
ret
Just 2 comments: Assuming MinGW32 is being used, the prolog/epilog isn't necessary... And WinMain@16 isn't necessary as well:
; test.asm
;
; nasm -fwin32 -o test.o test.asm
; gcc -o test.exe test.o
;
bits 32
extern _printf
extern _scanf
section .rodata
prmpt1: db 'Enter an integer: ',0
prmpt2: db `Your integer is %d\n`,0
format: db '%d',0
section .bss
value: resd 1
section .text
global _main
_main:
push prmpt1
call _printf
add esp,4*1
push value
push format
call _scanf
add esp,4*2
push dword [value]
push prmpt2
call _printf
add esp,4*2
ret
-
just commenting on ret to exit after "call printf"...
i observed that you can get away with calling printf and then do ret and also you beginning your (64-bit) code with sub sp,8.
in windows 10 and 11, codes with features that i mentioned will exit normally if executed on console.
in windows 7 with all the updates, the code will execute, display what it needs to display but the console will raise an error.
So it's still stuck and being true with the description of assembly language being not portable.
bonus: if you disassemble a .net executable, you'll notice that the size of the stack is 8.
(im talking about codes that are assembled in NASM and Linked with Microsoft Linker to natively run on Windows subsystem)
-
Why do you insist on using libc in a simple assembly program?
Yes... for Windows Console you'll need to use Win32 API, but you don't need libc (or msvcrt.dll):
Here's a simple Console program using ANSI codes for x86-64:
; test.asm
;
; nasm -fwin64 -o test.o test.asm
; x86_64-w64-mingw32-ld -s -o test.exe test.o -lkernel32
;
bits 64
default rel
%define ENABLE_VIRTUAL_TERMINAL_PROCESSING 4
%define STDOUT_HANDLE -11
section .bss
mode:
resd 1
section .rodata
msg:
db `\033[1;31mH\033[1;32me\033[1;33ml\033[1;34ml\033[1;35mo\033[m\n`
msg_len equ $ - msg
section .text
; Importadas de kernel32.dll
extern __imp_GetStdHandle
extern __imp_GetConsoleMode
extern __imp_SetConsoleMode
extern __imp_WriteConsoleA
extern __imp_ExitProcess
global _start
_start:
mov ecx,STDOUT_HANDLE
call [__imp_GetStdHandle]
mov rbx,rax ; Guarda handle.
; Habilita VT100 no Windows Terminal.
mov rcx,rax
lea rdx,[mode]
call [__imp_GetConsoleMode]
mov edx,[mode]
or edx,ENABLE_VIRTUAL_TERMINAL_PROCESSING
mov rcx,rbx
call [__imp_SetConsoleMode]
mov rcx,rbx
lea rdx,[msg]
mov r8d,msg_len
xor r9d,r9d
push 0
call [__imp_WriteConsoleA]
xor ecx,ecx
jmp [__imp_ExitProcess]
And here is the same code, but for win32:
; test32.asm
;
; nasm -fwin32 -o test.o test.asm
; i686-w64-mingw32-ld -s -o test.exe test.o -lkernel32
;
bits 32
%define ENABLE_VIRTUAL_TERMINAL_PROCESSING 4
%define STDOUT_HANDLE -11
section .bss
mode:
resd 1
section .rodata
msg:
db `\033[1;31mH\033[1;32me\033[1;33ml\033[1;34ml\033[1;35mo\033[m\n`
msg_len equ $ - msg
section .text
; Importadas de kernel32.dll
extern __imp__GetStdHandle@4
extern __imp__GetConsoleMode@8
extern __imp__SetConsoleMode@8
extern __imp__WriteConsoleA@20
extern __imp__ExitProcess@4
global _start
_start:
push STDOUT_HANDLE
call [__imp__GetStdHandle@4]
mov ebx,eax ; Store handle in ebx
; Habilita VT100 no Windows Terminal.
push mode
push eax
call [__imp__GetConsoleMode@8]
mov eax,[mode]
or eax,ENABLE_VIRTUAL_TERMINAL_PROCESSING
push eax
push ebx
call [__imp__SetConsoleMode@8]
push 0
push 0
push msg_len
push msg
push ebx
call [__imp__WriteConsoleA@20]
push 0
jmp [__imp__ExitProcess@4]
No libc required.
-
^^
printf() works wonders with "%s".
say you wanna lazy convert a set of characters using xor and you wanna convert them back again but the "distance" of say "-" to "_" will produce non-displayable characters when converting them back or sometimes displaying unwanted spaces and such, just pass them to "%s" and it will be in pure text.. it's like the clean up process for text conversion..
also print() is the first ever c routine that was used to hello world everyone done by brian kernighan..
you just push the text and push to "%s" and then call printf().
-
Here's my opinion... this is NOT assembly... this is C, disguised as assembly...
Let's look at both codes:
/* test.c */
int main(void){puts("Hello");return0;}
; test64.asm
bits 64
default rel
section .text
extern puts
global main
align 4
main:
sub rsp,8 ; aligning the stack, as per SysV ABI.
lea rdi,[hello]
call puts wrt ..plt
add rsp,8
ret
section .rodata
msg:
db 'Hello',0
Now compile both:
$ gcc -O2 -s -o test test.c
$ nasm -felf64 -o test64.o test64.asm
$ gcc -s -o test64 test64.o
$ ls -go test*
-rwxr-xr-x 1 14416 jan 6 16:23 test
-rwxr-xr-x 1 14416 jan 6 16:23 test64
-rw-r--r-- 1 201 jan 6 16:23 test64.asm
-rw-r--r-- 1 880 jan 6 16:23 test64.o
-rw-r--r-- 1 59 jan 6 16:28 test.c
There's NO difference!
This:
; test64-2.asm
bits 64
default rel
section .text
global _start
align 4
_start:
mov eax,1
mov edi,eax
lea rsi,[hello]
mov edx,hello_len
syscall
mov eax,60
xor eax,eax
syscall
section .rodata
hello:
db `Hello\n`
hello_len equ $ - hello
$ nasm -felf64 -o test64-2.o test64-2.asm
$ ld -s -o test64-2 test64-2.o
$ ls -go test64-2*
-rwxr-xr-x 1 8480 jan 6 16:31 test64-2
-rw-r--r-- 1 268 jan 6 16:30 test64-2.asm
-rw-r--r-- 1 880 jan 6 16:31 test64-2.o
Give you a image file almost 6 KiB smaller because all that code on c0.o (C Runtime) aren't being linked to your code.
-
^^ seems to be reversed in windows coz usually a portable win32(x64/x86_32) file is larger than any code that has functions that are dependent on outside libraries.
like you even need to install runtimes if you just wanna make your small program to execute a basic routine.
and yes, in some instances, a code could be smaller if it directly calls the built-in dlls like kernel32.dll or ntdll.dll.
but generally, for hlls, you need to install the language runtimes and if you want your code to run on a computer without the runtime, you have to make your code carry the runtime itself (or something like that)/
-
so if you just want to deploy a code that is heavy on C runtime usage on a certain OS (Windows 10 for example), get the appropriate Visual Studio version that builds Windows 10 programs by default. you won't concern or concern less if your code will run on Windows 10 coz it will surely can.
or just call the equivalent native APIs to do the same thing as your runtime dependent code and likely your code will be backwards-compatible.
-
You are not understanding what I've said.. "C Runtime" object file is the initialization file linked to EVERY executable (those extra 6 KiB). A lot of initialization is made for a C program to work and, since the "assembly" (in disguise) program uses libc functions, the initialization must be linked to your code...
There is also the MSVCRT.DLL and other files used by your code (take a look with ldd utility). In my exemple, after loaded, the program uses, let's say, 1 KiB of RAM, your C disguised as "assembly" program uses way more...
And I want to deploy a program with NO C runtime dependency and I do not use Visual Studio (argh!). On Windows platform the principle is the same: You can import Win32 API routines, which are independent from C Runtime. This will instruct the linker to load the DLLs using early binding. And will work on EVERY single one of Windows versions, since Windows NT or Windows 95 until Windows 11. No C Runtime needed.
And no... Pure assembly code will always be smaller than mixed asm & C Runtime initialization routines... For the same code, I mean...
-
well i can speak for windows coz i delved on windows assembly doing a short personal project.
when you throw an assembly code (win32 or variants) to a disassembler (ida64), it doesn't break down the printf() to pieces, as you were implying.
you can step into the printf() but that's already the code referencing the function and going through it in the msvcrt.dll.
but i might be mistaken, tho. i don't do linux programming or any linux "virtual machine to windows" programming such as ming64 as you were using linux code to explain your side. the OP was using linux assembly so. :D
-
well i can speak for windows coz i delved on windows assembly doing a short personal project.
when you throw an assembly code (win32 or variants) to a disassembler (ida64), it doesn't break down the printf() to pieces, as you were implying.
you can step into the printf() but that's already the code referencing the function and going through it in the msvcrt.dll.
Again.. I never said that... libc function are implemented somewhere, if not statically linked, then in a DLL (or shared object file). In the case of Windows, it is in MSVCRT.DLL or equivalent.
Use your debugger and see what your C disguised as assembly program do from the beginning (not main(), but the image entry point) and you'll see.
but i might be mistaken, tho. i don't do linux programming or any linux "virtual machine to windows" programming such as ming64 as you were using linux code to explain your side. the OP was using linux assembly so. :D
There's no difference. C Runtime code is still linked to yours, using MinGW, Cygwin or the crappy Visual Studio linker...
My point is: If you have to use C functions from libc, do your program in C. It'll generate better code than yours, probably.
-
A simple example:
; test.asm
; nasm -g -fwin64 -o test.o test.asm
; x86_64-w64-mingw32-gcc -g -o test.exe test.o
;
bits 64
default rel
section .rodata
str:
db 'Hello',0
section .text
extern puts
; I hope this works, I don't have Windows here to test.
global main
align 4
main:
xor eax,eax
push rax ; Align RSP and save return value.
lea rcx,[str]
call puts
pop rax
ret
After compiling and linking, using x86_64-w64-mingw32-objdump to get the actual code created:
$ x86_64-w64-mingw32-objdump -dM intel test.exe
See the dump file linked.
PS: I've added stack alignment later, there is 3 instructions missing in the listing (but I hope you get the idea)
-
As this is a NASM forum, I fail to see the point of posts arguing that C is better.
Given optimised code, and without using outside libraries, asm will always be smaller and/or faster. I know some C compilers will generate better optimisations than the average asm coder, but all being equal, asm is far superior, even if harder to gain competency in :)
-
As this is a NASM forum, I fail to see the point of posts arguing that C is better.
Given optimised code, and without using outside libraries, asm will always be smaller and/or faster. I know some C compilers will generate better optimisations than the average asm coder, but all being equal, asm is far superior, even if harder to gain competency in :)
Could be smaller (without linking to the entire C Runtime initialization and finalization code), but surely, most of the time won't be faster. As you said, modern C compilers optimizes things an experienced assembly programmer ignores, most of the time. For example: GCC considers minimizing branch prediction and cache misses penalties and improving instruction scheduling -- which is difficult to do by hand. Granted, sometimes the high level language do something redundant (or it seems to be), but, again, most of the time, the generate code is hard to beat, in terms of performance.
Btw, using printf to print a simple string is the slowest way to do it (measure the clock cycles using write syscall and printf!) AND the bloated way to do it as well...
My point: If someone wants to do a program in assembly, do it in assembly without using C functions as crutches. If someone wants to learn assembly, do everything in assembly. At maximum, use a library that don't depend on C runtime to work. Doing so is cheating, not "learning"...
Ahhh... I'm not saying that C cannot be used, is the other way around... In many cases my C routines are faster than the assembly equivalent (and I have 35 years of experience with asm!). In fact, I think if you want to do a complex function in assembly, do it first in C and take a look on what a good compiler creates, THEN you can optimize by hand (if there is some space for it).
[]s
Fred
-
These C compilers appear by magic, do they? Somebody must know their assembly language or the compilers wouldn't be any good, no?
What am I missing?
Best,
Frank
-
Not a good argument: NASM appeared by magic as well? A C compiler must exist BEFORE assembly, because its entire source code is in C (not ASM). And what about the first assembler? Who made it and in what language?
Another demonstration showing that using printf() is a bad idea:
; cycles.asm
;
; Yep, using libc!
;
; nasm -felf64 -o test.o test.asm
; ld -o test ./test
;
bits 64
default rel
section .bss
counter:
resq 1
section .rodata
hello:
db `Hello\n`
hello_len equ $ - hello
db 0
fmt1:
db `printf (%llu cycles)\n`,0
fmt2:
db `sys_write (%llu cycles)\n`,0
section .text
extern printf
global main
main:
sub rsp,8
; Measuring printf call.
xor eax,eax
cpuid ; serialize processor.
rdtsc
mov [counter],eax
mov [counter+4],edx
xor eax,eax
lea rdi,[hello]
call printf wrt ..plt
rdtscp
sub eax,[counter]
sbb edx,[counter+4]
shl rdx,32
mov esi,eax
add rsi,rdx
xor eax,eax
lea rdi,[fmt1]
call printf wrt ..plt
; Measuring sys_write.
xor eax,eax
cpuid ; serialize processor
rdtsc
mov [counter],eax
mov [counter+4],edx
mov eax,1
mov edi,eax
lea rsi,[hello]
mov edx,hello_len
syscall
rdtscp
sub eax,[counter]
sbb edx,[counter+4]
shl rdx,32
mov esi,eax
add rsi,rax
xor eax,eax
lea rdi,[fmt2]
call printf wrt ..plt
add rsp,8
ret
$ ./cycles
Hello
printf (215525 cycles)
Hello
sys_write (11004 cycles)
-
Not a good argument: NASM appeared by magic as well?
Of course not. Simon Tatham (and Julian Hall) worked hard to produce it.
A C compiler must exist BEFORE assembly, because its entire source code is in C (not ASM)
True of Nasm, yes. But...
. And what about the first assembler? Who made it and in what language?
Now that's a good question! Surely not C! Bare byte machine code, I would assume...
C, I understand, followed A and B... probably written in an AT&T assembler?
Another demonstration showing that using printf() is a bad idea:
I don't disagree with that. If you want printf to work in Windows, you use a library for Windows. If you want printf to work in Linux, you use a library for Linux. Etc. That's why Nasm is in C. Not for optimized code!
I dunno, Fred. I think we may appear to be arguing about something we actually agree about...
I appreciate your input!
Best,
Frank
-
k.
this is the original source made from NASM
~~~~~~~~~
;
; sp4ce.asm
;
; alCoPaUL [GIMO][As][aBrA][NPA][b8]
; 10/6/2021
;
; nasm <dash>f win32 sp4ce.asm
; link sp4ce.obj /subsystem:console /defaultlib:msvcrt.lib /entry:m
;
global m
extern _printf
section .text
m:mov al,10
mov bl,45
z:lea edx,a
mov cx,491
r:cmp byte[edx],bl
je s
jmp u
s:mov byte[edx],al
u:inc edx
dec cx
cmp cx,0
jnz r
push a
push i
call _printf
mov al,10
cmp bl,45
xchg al,bl
je z
push x
push i
call _printf
ret
section .data
x:db 2Ch,32h,37h,68h,2Ch,30h,0
i:db 25h,73h,0
a:db ';-; sp4ce.asm-;-; alCoPaUL [GIMO][As][aBrA][NPA][b8]-; 10/6/2021-;-; nasm <dash>f win32 sp4ce.asm-; link sp4ce.obj /subsystem:console /defaultlib:msvcrt.lib /entry:m-;-global m-extern _printf-section .text-m:mov al,10-mov bl,45-z:lea edx,a-mov cx,491-r:cmp byte[edx],bl-je s-jmp u-s:mov byte[edx],al-u:inc edx-dec cx-cmp cx,0-jnz r-push a-push i-call _printf-mov al,10-cmp bl,45-xchg al,bl-je z-push x-push i-call _printf-ret-section .data-x:db 2Ch,32h,37h,68h,2Ch,30h,0-i:db 25h,73h,0-a:db ',27h,0
~~~~~~~~~~~~~
this is the source generated from IDA..
;
; +-------------------------------------------------------------------------+
; | This file was generated by The Interactive Disassembler (IDA) |
; | Copyright (c) 2019 Hex-Rays, <support@hex-rays.com> |
; | License info: 48-256C-4840-25 |
; | Team-IRA Release [PUBLIC] ,v1.0 |
; +-------------------------------------------------------------------------+
;
; Input SHA256 : 15AFA3253DA6552C06CF72B15E59D8E7FB98F58D99C0A21F3B6BA529C535165E
; Input MD5 : 9A87262B71B81E2FCD380239CBB560A2
; Input CRC32 : 010A3443
; File Name : C:\Users\IWAYWarrior\Downloads\Project159_Windows11_NASM_VS2029\e\et\sp4ce.exe
; Format : Portable executable for 80386 (PE)
; Imagebase : 400000
; Timestamp : 616720C4 (Wed Oct 13 18:09:08 2021)
; Section 1. (virtual address 00001000)
; Virtual size : 0000004D ( 77.)
; Section size in file : 00000200 ( 512.)
; Offset to raw data for section: 00000400
; Flags 60000020: Text Executable Readable
; Alignment : default
.686p
.mmx
.model flat
; Segment type: Pure code
; Segment permissions: Read/Execute
_text segment para public 'CODE' use32
assume cs:_text
;org 401000h
assume es:nothing, ss:nothing, ds:_data, fs:nothing, gs:nothing
public start
start proc near
mov al, 0Ah
mov bl, 2Dh ; '-'
loc_401004:
lea edx, aSp4ceAsmAlcopa ; ";-; sp4ce.asm-;-; alCoPaUL [GIMO][As][a"...
mov cx, 1EBh
loc_40100E:
cmp [edx], bl
jz short loc_401014
jmp short loc_401016
loc_401014:
mov [edx], al
loc_401016:
inc edx
dec cx
cmp cx, 0
jnz short loc_40100E
push offset aSp4ceAsmAlcopa ; ";-; sp4ce.asm-;-; alCoPaUL [GIMO][As][a"...
push offset Format ; "%s"
call printf
mov al, 0Ah
cmp bl, 2Dh ; '-'
xchg al, bl
jz short loc_401004
push offset a27h0 ; ",27h,0"
push offset Format ; "%s"
call printf
retn
start endp ; sp-analysis failed
; [00000006 BYTES: COLLAPSED FUNCTION printf. PRESS CTRL-NUMPAD+ TO EXPAND]
align 200h
dd 380h dup(?)
_text ends
; Section 2. (virtual address 00002000)
; Virtual size : 00000160 ( 352.)
; Section size in file : 00000200 ( 512.)
; Offset to raw data for section: 00000600
; Flags 40000040: Data Readable
; Alignment : default
;
; Imports from MSVCR100.dll
;
; Segment type: Externs
; _idata
; int printf(const char *const Format, ...)
extrn __imp_printf:dword
; Debug Directory entries
; Segment type: Pure data
; Segment permissions: Read
_rdata segment para public 'DATA' use32
assume cs:_rdata
;org 402008h
dd 0 ; Characteristics
dd 616720C4h ; TimeDateStamp: Wed Oct 13 18:09:08 2021
dw 0 ; MajorVersion
dw 0 ; MinorVersion
dd 0Dh ; Type: IMAGE_DEBUG_TYPE_POGO
dd 0DCh ; SizeOfData
dd rva unk_40203C ; AddressOfRawData
dd 63Ch ; PointerToRawData
db 18h
db 0
db 0
db 0
db 0
db 80h ; €
db 0
db 80h ; €
db 0
db 0
db 0
db 0
db 0
db 0
db 0
db 0
db 0
db 0
db 0
db 0
db 0
db 0
db 0
db 0
; Debug information (IMAGE_DEBUG_TYPE_POGO)
unk_40203C db 0
db 0
db 0
db 0
db 0
db 10h
db 0
db 0
db 47h ; G
db 0
db 0
db 0
db 2Eh ; .
db 74h ; t
db 65h ; e
db 78h ; x
db 74h ; t
db 0
db 0
db 0
db 47h ; G
db 10h
db 0
db 0
db 6
db 0
db 0
db 0
db 2Eh ; .
db 74h ; t
db 65h ; e
db 78h ; x
db 74h ; t
db 24h ; $
db 6Dh ; m
db 6Eh ; n
db 0
db 0
db 0
db 0
db 0
db 20h
db 0
db 0
db 8
db 0
db 0
db 0
db 2Eh ; .
db 69h ; i
db 64h ; d
db 61h ; a
db 74h ; t
db 61h ; a
db 24h ; $
db 35h ; 5
db 0
db 0
db 0
db 0
db 8
db 20h
db 0
db 0
db 1Ch
db 0
db 0
db 0
db 2Eh ; .
db 72h ; r
db 64h ; d
db 61h ; a
db 74h ; t
db 61h ; a
db 0
db 0
db 24h ; $
db 20h
db 0
db 0
db 18h
db 0
db 0
db 0
db 2Eh ; .
db 72h ; r
db 64h ; d
db 61h ; a
db 74h ; t
db 61h ; a
db 24h ; $
db 76h ; v
db 6Fh ; o
db 6Ch ; l
db 74h ; t
db 6Dh ; m
db 64h ; d
db 0
db 0
db 0
db 3Ch ; <
db 20h
db 0
db 0
db 0DCh ; Ü
db 0
db 0
db 0
db 2Eh ; .
db 72h ; r
db 64h ; d
db 61h ; a
db 74h ; t
db 61h ; a
db 24h ; $
db 7Ah ; z
db 7Ah ; z
db 7Ah ; z
db 64h ; d
db 62h ; b
db 67h ; g
db 0
db 0
db 0
db 18h
db 21h ; !
db 0
db 0
db 14h
db 0
db 0
db 0
db 2Eh ; .
db 69h ; i
db 64h ; d
db 61h ; a
db 74h ; t
db 61h ; a
db 24h ; $
db 32h ; 2
db 0
db 0
db 0
db 0
db 2Ch ; ,
db 21h ; !
db 0
db 0
db 14h
db 0
db 0
db 0
db 2Eh ; .
db 69h ; i
db 64h ; d
db 61h ; a
db 74h ; t
db 61h ; a
db 24h ; $
db 33h ; 3
db 0
db 0
db 0
db 0
db 40h ; @
db 21h ; !
db 0
db 0
db 8
db 0
db 0
db 0
db 2Eh ; .
db 69h ; i
db 64h ; d
db 61h ; a
db 74h ; t
db 61h ; a
db 24h ; $
db 34h ; 4
db 0
db 0
db 0
db 0
db 48h ; H
db 21h ; !
db 0
db 0
db 18h
db 0
db 0
db 0
db 2Eh ; .
db 69h ; i
db 64h ; d
db 61h ; a
db 74h ; t
db 61h ; a
db 24h ; $
db 36h ; 6
db 0
db 0
db 0
db 0
db 0
db 30h ; 0
db 0
db 0
db 0F7h ; ÷
db 1
db 0
db 0
db 2Eh ; .
db 64h ; d
db 61h ; a
db 74h ; t
db 61h ; a
db 0
db 0
db 0
__IMPORT_DESCRIPTOR_MSVCR100 dd rva off_402140 ; Import Name Table
dd 0 ; Time stamp
dd 0 ; Forwarder Chain
dd rva aMsvcr100Dll ; DLL Name
dd rva __imp_printf ; Import Address Table
align 20h
;
; Import names for MSVCR100.dll
;
off_402140 dd rva word_402148
dd 0
word_402148 dw 5D7h
db 'printf',0
align 2
aMsvcr100Dll db 'MSVCR100.dll',0
align 1000h
_rdata ends
; Section 3. (virtual address 00003000)
; Virtual size : 000001F7 ( 503.)
; Section size in file : 00000200 ( 512.)
; Offset to raw data for section: 00000800
; Flags C0000040: Data Readable Writable
; Alignment : default
; Segment type: Pure data
; Segment permissions: Read/Write
_data segment para public 'DATA' use32
assume cs:_data
;org 403000h
a27h0 db ',27h,0',0
; char Format[]
Format db '%s',0
aSp4ceAsmAlcopa db ';-; sp4ce.asm-;-; alCoPaUL [GIMO][A'
db 's][aBrA][NPA][b8]-; 10/6/2021-;-; n'
db 'asm <dash>f win32 sp4ce.asm-; link '
db 'sp4ce.obj /subsystem:console /defau'
db 'ltlib:msvcrt.lib /entry:m-;-global '
db 'm-extern _printf-section .text-m:mo'
db 'v al,10-mov bl,45-z:lea edx,a-mov c'
db 'x,491-r:cmp byte[edx],bl-je s-jmp u'
db '-s:mov byte[edx],al-u:inc edx-dec c'
db 'x-cmp cx,0-jnz r-push a-push i-call'
db ' _printf-mov al,10-cmp bl,45-xchg a'
db 'l,bl-je z-push x-push i-call _print'
db 'f-ret-section .data-x:db 2Ch,32h,37'
db 'h,68h,2Ch,30h,0-i:db 25h,73h,0-a:db'
db ' ',27h,0
align 1000h
_data ends
end start
~~~~~~
so printf() is not broken down to pieces as you imply BUT is listed "by function" for EXTERNAL use,
as i saw, you converted the .asm object file to linux executable using GCC and compared it to the generated linux executable with different routines (with SYSCALL) to display texts using ld. you can link your print() assembled file using ld.exe WITH the appropriate C runtime library with your linux object file. try that and compare again,.
edit: the actual runtime for executing the code is MSVCR100.dll and you need to link the .obj file to the generic .dll file for the appropriate runtime file version (e.g. msvcrt.dll for Visual Studio 2010 C/C++).
edit1: grammar, etc.
-
and to add.
you saw that the source that i posted with printf() is not made with #include <whatever> and curly braces. it uses opcodes and numbers.
and you sound that you're complaining for the fact that printf() did some epic stuff so you wanna discredit it by comparing an assembled code with printf() that you linked with GCC to an assembled code with SYSCALL and linking it with ld.exe.
as i said, try linking your printf() assembled file with ld.exe coz you can do that.
like yo, you've programmed assembly for 35 years and suddenly an assembly code using printf() is not assembly language??
like wtf lelz men.
-
Let's play a game then... Let's make a small program. You do your way, I do mine...
Wins who made them SMALLER and running FASTER.
And for Windows (argh!)...
I'll wait for a proposal of what to do...
BTW: That code above doesn't work (pushing arguments to _printf and not correcting ESP after the call will cause a seg fault). There are other errors.
I'll show what you're doing wrong with that IDA listing later (When I got my hands on WINDOWS and VS - I don't have them at home).
-
BWAHAHAHAHAHAHAHAHA... Your code isn't compiling! There is no _printf on MSVCRT!
C:\Windows\System32>dumpbin /exports msvcr100.dll | findstr _printf
640 27F 000656F1 _get_printf_count_output
981 3D4 0006564C _printf_l
982 3D5 000656AF _printf_p
983 3D6 00065696 _printf_p_l
984 3D7 00065665 _printf_s_l
1031 406 000656C7 _set_printf_count_output
Same for msvcrt.dll
C:\Work\tmp> link test.obj /defaultlib:msvcrt.lib /subsystem:console
link test.obj /defaultlib:msvcrt.lib /entry:start /subsystem:console /entry:start
Microsoft (R) Incremental Linker Version 14.29.30147.0
Copyright (C) Microsoft Corporation. All rights reserved.
test.obj : error LNK2001: símbolo externo não resolvido _printf
test.exe : fatal error LNK1120: 1 externo não resolvidos
Yep... LIB and INCLUDE environment vars are correct.
-
Here's how to do it:
; test.asm
;
; nasm -fwin32 -o test.obj test.asm
; link test.obj /debug:none /release /subsystem:console msvcrt.lib legacy_stdio_definitions.lib
;
bits 32
section .rodata
str:
db `Hello\n`,0
section .text
; Older MSVCRT exports _cprintf, but you must link with legacy_stdio_definitions.lib.
extern __cprintf
global _main
align 4
_main:
push str
call __cprintf
add esp,4
ret
Now, take a look at:
C:\Work> dumpbin /disasm test.exe
3308 bytes in .text, 512 bytes in .data, 2704 bytes in .rodata and 344 in relocations in a simple 'Hello world' program. And lots of code!
Now, this one, to mimic your previous code (with CORRECTIONS):
; test.asm
;
; Better approach WITHOUT using libc!
; Why better? Because this code don't need to load MSVCRT???.DLL and all C initialization code.
;
; Unfortunately Windows Console applications must use Console API from kernel32.dll.
;
; nasm -fwin32 hello.asm -o hello.o
;
; if using Visual Studio:
; link hello.o /debug:none /release /subsystem:console /defaultlib:kernel32.lib /entry:start
; or, with mingw32:
; ld -s -o hello.exe hello.o -lkernel32
;
; They will create the same image...
;
; Yep, it is the default, but I like to be explicit.
bits 32
; Handle put in .bss not to occupy space in the image.
section .bss
handle:
resd 1
; Notice these buffers won't be changed!
section .rodata
; Little test: Will change '-' to `\r\n` on terminal.
str:
db `Hello-World-`
str_len equ $ - str
crlf:
db `\r\n`
crlf_len equ $ - crlf
section .text
; As defined in Win32 Console API.
%define STD_OUTPUT_HANDLE -11
extern __imp__GetStdHandle@4
extern __imp__WriteConsoleA@20
extern __imp__ExitProcess@4
global _start
align 4 ; always a good ideia to align entrypoints.
_start:
%ifdef OBEY_ABI
push ebx ; Obey MS-ABI and preserve EBX and EDI!
push edi ; Not really necessary here!
%endif
; Gets STDOUT_HANDLE from Console API.
push byte STD_OUTPUT_HANDLE
call [__imp__GetStdHandle@4]
mov [handle],eax
; prepare to scan the string searching for '-' in the loop below.
lea edi,[str]
mov ecx,str_len
align 4 ; always a good idea to align LOOPs entrypoints.
.loop:
mov al,'-'
mov ebx,edi ; keep track of the substring start
repne scasb
jne .exit ; Not found, exit the loop.
call print_substring
call print_crlf
jmp .loop
.exit:
; --- Exit from program
%ifdef OBEY_ABI
pop edi ; Restore EBX and EDI.
pop ebx ; Not really necessary here!
%endif
; Don't need to close the stdout handle!
; Must exit jumping to ExitProceess!
push byte 0
jmp [__imp__ExitProcess@4]
; Prints the substring not including the '-' found.
; Entry: EBX = start of substring,
; EDI = one char past the '-' found.
; Destroys EAX (at least).
; Preserve ECX to continue scanning.
align 4
print_substring:
push ecx
lea eax,[edi-1]
sub eax,ebx
jz .exit
push byte 0
push byte 0
push eax
push ebx
push dword [handle]
call [__imp__WriteConsoleA@20] ; Since ABI allows changes no ECX it was preserved earlier.
.exit:
pop ecx
ret
; Just print CRLF. Preserve ECX to conitnue scanning.
align 4
print_crlf:
push ecx
push byte 0
push byte 0
push crlf_len
push crlf
push dword [handle]
call [__imp__WriteConsoleA@20] ; Since ABI allows changes no ECX it was preserved earlier.
pop ecx
ret
124 bytes in .text, 0 bytes in .data, 4 bytes in .bss, 14 bytes in .rodata, 28 bytes in relocations (7 relocations), and 94 bytes in imported ptrs (from kernel32.dll)...
C:\Work> dir *.exe
Volume in drive C is OS
Volume Serial Number is 9E55-4C71
Directory of C:\Work
09/01/2023 12:03 3.072 test.exe
09/01/2023 12:31 9.216 test2.exe
Again, 6 KiB of unecessary code and data to initialize libc... blagh!
-
~~~~~~~~~
;
; sp4ce.asm
;
; alCoPaUL [GIMO][As][aBrA][NPA][b8]
; 10/6/2021
;
; nasm <dash>f win32 sp4ce.asm
; link sp4ce.obj /subsystem:console /defaultlib:msvcrt.lib /entry:m
;
you need msvcrt.lib..
bro, like wtf.. of course my code isn't compiling coz it's assembling..
go get the sdk for visual studio 2010 and get the appropriate .libs
-
and yo, did you even compare my posted source code, like the way it was written or how the control flow flows to your almost linear assembly structure?
like if you didn't delete my post in the EXAMPLE CODE section and actually tested it in a spare isolated computer, you should've had everything there to assemble properly and see what's up.
printf() is not that different with win32 apis. you can find printf() in the runtime dll, you can find win32 api from kernel32.dll
in linux, you just go syscalling forever to meet your needs (at least ms-dos goes INT (number here))
-
and again, you can use ld.exe with the appropriate C runtime lib in linux to link your nasm assembly object code that has printf().
you were insisting that those files are the same coz why? you used GCC in both - the C printf() hello world template and the assembly language code with printf().
and bro, you had the audacity to compare the file size of the assembly file that has printf() that you GCCed and the one that you LDed that has the SYSCALL meme.
go link the assembly code that has the printf() using LD.exe with it's appropriate library and let's talk after.