Your code snippet would return -1, as a char. If you cast to unsigned char, you'd (usually) get 255, but that's implementation-defined (most systems are two's complement, but it's not defined by the C spec).

putc() takes an int as its first argument, so.. no, you don't need to cast. The manual says it casts that to an unsigned char before writing.

You refer to the ABI to see what bit width an int has. On x86 (and x86_64), it's 32 bits. long is 64 bits wide, and short is 16.
christop wrote:
ephan wrote:
So I decided to use []'s to get the length of the string (first byte of the adress in memory of the string is the size):

I'm not sure where you learned that brackets would get the length of a string. They don't. Brackets around an address/symbol or a register dereferences the value stored at that address/register. It's basically like doing "ld a,(hl)" which loads the value that hl points to.


Did you read what was inside the parentheses? Maybe you should Wink

Either way, after reading all of what you guys discussed, I decided to get down to coding:


Code:
section .data
    theinput:  resb 128                ; Declare string input
   
section .text
    global _start

_start:
    mov ebx, 1            ; File descriptor 1 - standard output
   
    mov eax, 3            ; The system call for read (sys_read)
    mov ecx, theinput     ; Variable where input is saved
    mov edx, 4            ; Length in bytes of the input
    int 80h
   
    mov eax,4             ; The system call for write (sys_write)
    mov eax, 4            ; For the system call write
    mov ecx, theinput     ; Save the variable to write in ecx
    mov edx, 127          ; Get input length
    ;movb [theinput+127], 0 Why would I need this, NASM gives error
    int 80h
   
    mov eax, 1            ; The system call for exit (sys_exit)
    mov ebx, 0            ; Exit with return code of 0 (no error)
    int 80h               ; Exit program


This works like a cat program, but only for strings with less than 4 characters, NASM also gives the following output:

Quote:
hello.asm:2: warning: uninitialized space declared in non-BSS section `.data': zeroing


I think it zeroed the string, but I'm not quite sure.

There's also this line:


Code:
;movb [theinput+127], 0 Why would I need this, NASM gives error


I also got this piece of code from Tari, but he reserved 64 bytes, while I reserved 128.

Also, I can't seem to use push in x86-64, NASM says it's not supported in 64 mode.
ephan wrote:
christop wrote:
ephan wrote:
So I decided to use []'s to get the length of the string (first byte of the adress in memory of the string is the size):

I'm not sure where you learned that brackets would get the length of a string. They don't. Brackets around an address/symbol or a register dereferences the value stored at that address/register. It's basically like doing "ld a,(hl)" which loads the value that hl points to.


Did you read what was inside the parentheses? Maybe you should Wink
Did you read what we said? The size isn't stored anywhere. You would have to use strlen() and save that somewhere, and then you would need to remember to update that size each time you update the string. It isn't worth it.

Regarding the rest of it, I don't really know. But I do think it is odd that you keep trying to read from stdout.
ephan wrote:
Quote:
hello.asm:2: warning: uninitialized space declared in non-BSS section `.data': zeroing

Uninitialized data traditionally goes in .bss. It doesn't matter much where you put data, as long as it's paged read-write and ideally not executable.

ephan wrote:

Code:
;movb [theinput+127], 0 Why would I need this, NASM gives error

That dumps a null terminator at the last byte of the input, using the typical convention of null-terminating string. Disclaimer saves the day, I was mixing Intel and AT&T syntax. Make it 'mov byte ..'.

ephan wrote:
Also, I can't seem to use push in x86-64, NASM says it's not supported in 64 mode.

push is implicitly 64-bit in 64-bit mode. Straight 'push eax' is illegal in 64-bit mode since you're telling it to push a 32-bit register into a stack slot that's 64 bits wide. Either push rax or manually manipulate rsp (prefer the former).

That also implies you're trying to build the same code for 32 and 64-bit binaries, which is totally wrong (nasm -f elf vs nasm -f elf64). At the very least, you need to use the syscall instruction rather than int 80.

Finally, I don't feel like being anyone's personal debugger. Make gdb your friend.

Code:
[tari@Kirishima]$ nasm -g -f elf64 -o cat.o cat.S && ld -o cat cat.o
[tari@Kirishima]$ gdb cat
GNU gdb (GDB) 7.2
Copyright (C) 2010 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.  Type "show copying"
and "show warranty" for details.
This GDB was configured as "x86_64-unknown-linux-gnu".
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>...
Reading symbols from /home/tari/cat...done.
(gdb) break _start
Breakpoint 1 at 0x4000b0
(gdb) run
Starting program: /home/tari/cat

Breakpoint 1, 0x00000000004000b0 in _start ()
(gdb) ni
0x00000000004000b5 in _start ()
(gdb) info registers
rax            0x4      4
rbx            0x0      0
rcx            0x0      0
rdx            0x0      0
rsi            0x0      0
rdi            0x0      0
rbp            0x0      0x0
rsp            0x7fffffffeae0   0x7fffffffeae0
r8             0x0      0
r9             0x0      0
r10            0x0      0
r11            0x200    512
r12            0x0      0
r13            0x0      0
r14            0x0      0
r15            0x0      0
rip            0x4000b5 0x4000b5 <_start+5>
eflags         0x202    [ IF ]
cs             0x33     51
ss             0x2b     43
ds             0x0      0
es             0x0      0
fs             0x0      0
gs             0x0      0
(gdb) <blah blah blah>
(gdb) quit
Here's a version of your code that reads up to 128 bytes and prints exactly as many bytes as are input:

Code:

section .bss

theinput:  resb 128                ; Declare string input

section .text

     global _start
_start:
     mov eax, 3            ; The system call for read (sys_read)
     mov ebx, 0            ; File descriptor 0 - standard intput
     mov ecx, theinput     ; Variable where input is saved
     mov edx, 128          ; Length in bytes of the input
     int 80h

     mov edx,eax           ; input length (read system call returns
                           ; the number of bytes that it read in eax)
     mov eax,4             ; The system call for write (sys_write)
     mov ebx, 1            ; File descriptor 1 - standard output
     mov ecx, theinput     ; Save the variable to write in ecx
     int 80h

     mov eax, 1            ; The system call for exit (sys_exit)
     mov ebx, 0            ; Exit with return code of 0 (no error)
     int 80h               ; Exit program

Notice that I moved eax into edx between the read and the write calls. That's the number of bytes that were input which we want to use for writing.

If you wanted to write a more functional "cat" program, you would add a jump instruction (eg, jmp) after the write system call to go back to the beginning, and also add a conditional jump after the read system call. If eax is zero or less than zero after the read system call, then jump to the exit syscall. Zero means end of file, and less than zero means there was an error while reading, and for either case you want to exit.
One of my problems was the file descriptor for stdout (now I see it's 0, thanks christop), which I couldn't find online.

Thank you a lot for the sample Smile

I tried something a bit more complex now:


Code:
section .bss
    username:  resb 128                ; Declare username input
    password:  resb 128                ; Declare password input

section .data
    usernameText: db "Username: "      ; Declare the text Username variable
    passwordText: db "Password: "      ; Declare the text Password variable

section .text
     global _start

_start:
     mov eax, 3            ; The system call for read (sys_read)
     mov ebx, 0            ; File descriptor 0 - standard input
     mov ecx, username     ; Variable where input is saved
     mov edx, 128          ; Length in bytes of the input
     int 80h
     mov edx, eax          ; input length (read system call returns
                           ; the number of bytes that it read in eax)
     
     mov eax, 3            ; The system call for read (sys_read)
     mov ebx, 0            ; File descriptor 0 - standard input
     mov ecx, password     ; Variable where input is saved
     mov edx, 128          ; Length in bytes of the input
     int 80h
     mov edx, eax          ; input length (read system call returns
                           ; the number of bytes that it read in eax)
     
     mov eax, 4
     mov ebx,1
     mov ecx, usernameText
     int 80h
     
     mov eax, 4            ; The system call for write (sys_write)
     mov ebx, 1            ; File descriptor 1 - standard output
     mov ecx, username     ; Save the variable to write in ecx
     int 80h
     
     mov eax, 4
     mov ebx,1
     mov ecx, passwordText
     int 80h
     
     mov eax, 4            ; The system call for write (sys_write)
     mov ebx, 1            ; File descriptor 1 - standard output
     mov ecx, password     ; Save the variable to write in ecx
     int 80h

     mov eax, 1            ; The system call for exit (sys_exit)
     mov ebx, 0            ; Exit with return code of 0 (no error)
     int 80h               ; Exit program


But here's a sample run:


Code:
ephan // I write "ephan"
mysecretpassword // I write "mysecretpassword"
Username: Passworephan
Password: ephan
mysecretpassword


I coded it so that the output would be:


Code:

Username: ephan
Password: mysecretpassword
Here are the standard file descriptors:
  • 0=stdin
  • 1=stdout
  • 2=stderr

Even Wikipedia lists these file descriptors. Smile
The first thing that I notice is that your strings aren't null-terminated, so that may be part of your problem.
_player1537 wrote:
The first thing that I notice is that your strings aren't null-terminated, so that may be part of your problem.


Thanks a lot! I tried to fix it:


Code:
section .bss
    username:  resb 128                ; Declare username input
    password:  resb 128                ; Declare password input

section .data
    usernameText: db "Username: "      ; Declare the text Username variable
    passwordText: db "Password: "      ; Declare the text Password variable

    usernameTextSize: equ $-usernameText ; Length of usernameText
    passwordTextSize: equ $-passwordText ; Length of passwordText

section .text
     global _start

_start:
     mov eax, 3            ; The system call for read (sys_read)
     mov ebx, 0            ; File descriptor 0 - standard input
     mov ecx, username     ; Variable where input is saved
     mov edx, 128          ; Length in bytes of the input
     int 80h
     mov esi, eax
     
     mov eax, 3            ; The system call for read (sys_read)
     mov ebx, 0            ; File descriptor 0 - standard input
     mov ecx, password     ; Variable where input is saved
     mov edx, 128          ; Length in bytes of the input
     int 80h
     mov edi, eax
     
     mov eax, 4
     mov ebx,1
     mov ecx, usernameText
     mov edx, usernameTextSize
     int 80h
     
     mov eax, 4            ; The system call for write (sys_write)
     mov ebx, 1            ; File descriptor 1 - standard output
     mov ecx, username     ; Save the variable to write in ecx
     mov edx, esi
     int 80h
     
     mov eax, 4
     mov ebx,1
     mov ecx, passwordText
     mov edx, passwordTextSize
     int 80h
     
     mov eax, 4            ; The system call for write (sys_write)
     mov ebx, 1            ; File descriptor 1 - standard output
     mov ecx, password     ; Save the variable to write in ecx
     mov edx, edi
     int 80h

     mov eax, 1            ; The system call for exit (sys_exit)
     mov ebx, 0            ; Exit with return code of 0 (no error)
     int 80h               ; Exit program



However, here's an example run:

Quote:
ephan
mysecretpassword
Username: Password: ephan
Password: mysecretpa


I don't understand why it's outputing "Username: Password: " in the first line of output.

It also doesn't fully output "mysecretpassword", it only does "mysecretpa".
Oh, because in this line

Code:
    usernameTextSize: equ $-usernameText ; Length of usernameText

you are subtracting the location of usernameText from the location of the current position of the code, which includes Username: and Password:. Move that line to right before this line.

Code:
    passwordText: db "Password: "      ; Declare the text Password variable
ephan wrote:
One of my problems was the file descriptor for stdout (now I see it's 0, thanks christop), which I couldn't find online.

Thank you a lot for the sample Smile

I tried something a bit more complex now:


Code:
section .bss
    username:  resb 128                ; Declare username input
    password:  resb 128                ; Declare password input

section .data
    usernameText: db "Username: "      ; Declare the text Username variable
    passwordText: db "Password: "      ; Declare the text Password variable

section .text
     global _start

_start:
     mov eax, 3            ; The system call for read (sys_read)
     mov ebx, 0            ; File descriptor 0 - standard input
     mov ecx, username     ; Variable where input is saved
     mov edx, 128          ; Length in bytes of the input
     int 80h
     mov edx, eax          ; input length (read system call returns
                           ; the number of bytes that it read in eax)
     
     mov eax, 3            ; The system call for read (sys_read)
     mov ebx, 0            ; File descriptor 0 - standard input
     mov ecx, password     ; Variable where input is saved
     mov edx, 128          ; Length in bytes of the input
     int 80h
     mov edx, eax          ; input length (read system call returns
                           ; the number of bytes that it read in eax)
     
     mov eax, 4
     mov ebx,1
     mov ecx, usernameText
     int 80h
     
     mov eax, 4            ; The system call for write (sys_write)
     mov ebx, 1            ; File descriptor 1 - standard output
     mov ecx, username     ; Save the variable to write in ecx
     int 80h
     
     mov eax, 4
     mov ebx,1
     mov ecx, passwordText
     int 80h
     
     mov eax, 4            ; The system call for write (sys_write)
     mov ebx, 1            ; File descriptor 1 - standard output
     mov ecx, password     ; Save the variable to write in ecx
     int 80h

     mov eax, 1            ; The system call for exit (sys_exit)
     mov ebx, 0            ; Exit with return code of 0 (no error)
     int 80h               ; Exit program


But here's a sample run:


Code:
ephan // I write "ephan"
mysecretpassword // I write "mysecretpassword"
Username: Passworephan
Password: ephan
mysecretpassword


I coded it so that the output would be:


Code:

Username: ephan
Password: mysecretpassword



Code:

     mov eax, 4
     mov ebx,1
     mov ecx, usernameText
     int 80h

You didn't specify the number of bytes to write. You need to load the correct value into edx ("Username: " is 10 bytes) before calling the write system call. Same thing with the other calls to write. In your code, you're putting the length of the password input into edx, which is 17 ("mysecretpassword" plus a newline character), so the write syscall prints out 17 bytes starting at "Username: ". The text "Password: " follows right after that in memory, so the write prints it all out contiguously.

These system calls don't care about null-terminated strings. They take a starting address and a byte count, and they return how many bytes they actually read or wrote in the eax register. In most cases, writes will write as many bytes as you told it to write, but when reading from a terminal that is in "canonical" mode (which is the default mode), the read system call will read only one line of data from the user at a time. (You can put the terminal, or rather the tty, into other modes which will give the read system call different behaviors, such as returning immediately when a character is typed, but I won't go into those modes right now).

EDIT: Oh, I see you fixed that by saving the input lengths and using them in each write call. You must have done that while I was writing my original post. Smile Instead of using edi and esi, you could also save the input lengths (in eax) to variables.
Quote:
EDIT: Oh, I see you fixed that by saving the input lengths and using them in each write call. You must have done that while I was writing my original post. Instead of using edi and esi, you could also save the input lengths (in eax) to variables.



Code:
section .bss
    username:  resb 128                ; Declare username input
    password:  resb 128                ; Declare password input

section .data
    usernameText: db "Username: "        ; Declare the text Username variable
    usernameTextSize: equ $-usernameText ; Length of usernameText
   
    passwordText: db "Password: "        ; Declare the text Password variable
    passwordTextSize: equ $-passwordText ; Length of passwordText

section .text
     global _start

_start:
     mov eax, 3            ; The system call for read (sys_read)
     mov ebx, 0            ; File descriptor 0 - standard input
     mov ecx, username     ; Variable where input is saved
     mov edx, 128          ; Length in bytes of the input
     int 80h
     mov esi, eax
     
     mov eax, 3            ; The system call for read (sys_read)
     mov ebx, 0            ; File descriptor 0 - standard input
     mov ecx, password     ; Variable where input is saved
     mov edx, 128          ; Length in bytes of the input
     int 80h
     mov edi, eax
     
     mov eax, 4                 ; The system call for write (sys_write)
     mov ebx,1                  ; File descriptor 1 - standard output
     mov ecx, usernameText      ; Save the variable to write in ecx
     mov edx, usernameTextSize  ; Length in bytes of the output
     int 80h
     
     mov eax, 4            ; The system call for write (sys_write)
     mov ebx, 1            ; File descriptor 1 - standard output
     mov ecx, username     ; Save the variable to write in ecx
     mov edx, esi
     int 80h
     
     mov eax, 4                 ; The system call for write (sys_write)
     mov ebx,1                  ; File descriptor 1 - standard output
     mov ecx, passwordText      ; Save the variable to write in ecx
     mov edx, passwordTextSize  ; Length in bytes of the output
     int 80h
     
     mov eax, 4            ; The system call for write (sys_write)
     mov ebx, 1            ; File descriptor 1 - standard output
     mov ecx, password     ; Save the variable to write in ecx
     mov edx, edi
     int 80h

     mov eax, 1            ; The system call for exit (sys_exit)
     mov ebx, 0            ; Exit with return code of 0 (no error)
     int 80h               ; Exit program


This works very well, I enter username and password and it outputs just as I want it Smile

However, I'm looking for a way to save the size of input and output in variables.

I also want to know how to use this code inside a C program.
ephan wrote:
However, I'm looking for a way to save the size of input and output in variables.

That's pretty easy. You're already using variables for the username and password. Just declare two more variables (eg, usernamesize and passwordsize) as 4 bytes each (ie, 32 bits), and then replace "esi" with "[usernamesize]" and "edi" with "[passwordsize]". I just did this and it works the same as yours does.

ephan wrote:
I also want to know how to use this code inside a C program.

There are at least two ways to do this: have separate C and asm source files, or have inline asm in your C source file. I prefer having them separate (some people prefer having them together). I'll explain how to do it with separate source files.

First you would have to change the last system call (the exit call) to a "ret" statement. Then you need to change the _start label to something else. Whatever name you choose, that is how you will call the function from C. For example, if you call it "get_username_and_password", you will call it as "get_username_and_password()" in C.

If you want to return a value, load the return value in eax before you return from the function. This works for integers (char, int, and long) and pointer return types, but not for more complex types like structs; I don't know the exact method to return structs, and it's generally not a good idea to return them from a function like that anyway.

Then there's the issue of mixing read()/write() calls with the stdio functions like scanf()/printf()/fread()/fwrite(). The stdio functions use read()/write() to do their work, but they also buffer their output and read ahead their input, which might lead to your input/output being out of the expected order if you mix system calls and stdio calls. You can avoid the problems with buffered output by calling fflush() before calling write(). However, mixing the input stdio calls and read system call can still be an issue.
christop wrote:
First you would have to change the last system call (the exit call) to a "ret" statement. Then you need to change the _start label to something else. Whatever name you choose, that is how you will call the function from C. For example, if you call it "get_username_and_password", you will call it as "get_username_and_password()" in C.

Prepend an underscore to the name in your assembly source if you're doing that, since the compiler will expect to find the symbol '_foo' if you prototype some function 'foo'.

Take care to obey the system's ABI as well. In particular, ebx, esi and edi are callee-saved on 32-bit Linux, so if you modify those without returning them to their original values things will probably blow up in an unpredictable fashion.

If you expect to take any arguments, you'll need to follow the calling conventions (cdecl by default on 32-bit Linux) as well. That also happens to define how to return large values (floats, any other types longer than 32 bits..).

I think inlining assembly tends to be a more useful approach, and should in most cases be a last optimization step. Trying to write any assembly before the program works and you've identified performance issues is premature optimization in my mind. Not that the same axiom really holds when you've specifically chosen to write a things in assembly, though. The advantage of writing a whole routine inline in assembly is that you don't need to worry about calling conventions- you can specify parameter names and all that jazz and let the compiler work out offsets and such.
It's a pity the OS doesn't let you get close enough to the hardware to talk to the screen driver... Sad
So what happens when you have some malware that renders pr0n onscreen instead of whatever you actually wanted? It would be nearly impossible to remove, especially on a system without any remote access provisions installed.

Keeping random programs from playing directly with the hardware is a good thing. Not only from the security standpoint, but also from the stability standpoint. What happens when your program that's talking directly to the display hardware crashes? The system is left in an indeterminate state. Even worse, you could set the video output to a mode unsupported by the display device and damage the hardware (not so much on new displays, but you can certainly do that to older CRTs).

If you really want to play with the display, Linux will allow you to mess with the framebuffer devices. Memory-map the device node (eg /dev/fb0) and treat it like a memory-mapped IO device that draws to the screen.
That or you can just work in X and display things to fit your whims..
I never said it wasn't a good thing that OSes abstract from the hardware, just that it's a lot less fun to program with that abstraction. Really, the greatest strength of Assembly is in working with hardware. By not permitting hardware access (which is definitely a good thing for security. MS-DOS would be a mess now...), you essentially eliminate the greatest strength of the language. Well written ASM is definitely faster than any equivalent compiler output, but that's like saying people use C because it has pointers. Sure, it's a useful feature, but that's not the strength of the language.
I won't disagree there- assembly is significantly less interesting when you don't get to play with hardware. That's a great reason to start playing with embedded systems and microcontrollers!
Qwerty.55 wrote:
It's a pity the OS doesn't let you get close enough to the hardware to talk to the screen driver... Sad


No, no it isn't. Besides, even the OS doesn't get to draw directly onto the screen - only the video driver does, and how it manages to do that is extremely complicated or complete undocumented. Nvidia, for example, will never tell you how to talk to their video cards. AMD will, so if you want to go dig through the documentation be my guest. Be warned, though, *every* generation and type of GPU has it's own rules, limitations, requirements, and protocols.

If you really want to run "on the metal", go write a kernel.

Also, unchecked hardware access is pretty easy to *kill* your hardware. Yeah, I'm sure your really eager to run on the metal now, right? The idea of bricking your computer sounds like loads of fun, right?
Qwerty.55 wrote:
It's a pity the OS doesn't let you get close enough to the hardware to talk to the screen driver... Sad
Back in 8th grade when I was writing x86 ASM, I remember that you could at least full-screen the command window and address characters, setting the background color, foreground color, and character, for pretty fun and nifty graphics.
  
Register to Join the Conversation
Have your own thoughts to add to this or any other topic? Want to ask a question, offer a suggestion, share your own programs and projects, upload a file to the file archives, get help with calculator and computer programming, or simply chat with like-minded coders and tech and calculator enthusiasts via the site-wide AJAX SAX widget? Registration for a free Cemetech account only takes a minute.

» Go to Registration page
Page 3 of 5
» All times are UTC - 5 Hours
 
You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum
You cannot vote in polls in this forum

 

Advertisement