2
\$\begingroup\$

I'm trying to code a simple person database using structs and arrays in assembly.

Each person has a firstName, lastName, age and weight. The goal is to have an infinite loop that asks the user whether they want to read or write to the array then ask them for a position to read or write to. Currently, the array is limited to a max of 5, plan is to increase that (as well as move the request for the position out both the .writing and .reading blocks).

A couple of questions:

  1. Formatting: Is the overall structure of the assembly okay? Any improvements that could be made to the formatting and structure?
  2. Accessing the elements of the array: For simple integer arrays, you can access them with [rax + rcx * (size of int)] where rax is the pointer to the start of the array and rcx is the position in the array we want to access. That didn't work for the Person array; I couldn't use [rax + rcx * Person.size]. So is there a better way to do that access?
  3. Other comments in general?

Compiled as follows using VS2017 x64 Native Tools Command Prompt:

> nasm -g -fwin64 person.asm
> cl /Zi person.obj msvcrt.lib legacy_stdio_definitions.lib

person.asm

default rel
extern scanf
extern printf
STRUC Person
 .first_name: resb 50
 .last_name: resb 50
 .age: resq 1
 .weight: resq 1
 .size:
ENDSTRUC
SECTION .data
 number: dq 0
 scanf_fmt_1: db " %s", 0
 scanf_fmt_2: db " %lld", 0
 scanf_fmt_3: db " %c", 0
 printf_fmt_1: db "Enter a number (1-5): ", 0
 printf_fmt_2: db "Enter a first_name: ", 0
 printf_fmt_3: db "Enter a last_name: ", 0
 printf_fmt_4: db "Enter an age: ", 0
 printf_fmt_5: db "Enter a weight: ", 0
 printf_fmt_6: db "[R]ead or [W]rite? ", 0
 printf_fmt_7: db "Person (%s %s) is %d years old and weights %d", 10, 0
SECTION .bss
 person_array: resb Person.size * 5
SECTION .text
global main
main:
 sub rsp, 32
.continue_loop:
 lea rax, [p] ; load address of p (or John Doe)
 lea rcx, [printf_fmt_6] ; address of "[R]ead or [W]rite? "
 call printf
 lea rdx, [number] ; address of number variable
 lea rcx, [scanf_fmt_3] ; address of " %c"
 call scanf
 cmp qword [number], 'W' ; compare read value against W
 je .writing ; local writing label
 jmp .reading ; local reading label
.writing:
 lea rcx, [printf_fmt_1] ; address of "Enter a number (1-5): "
 call printf
 lea rdx, [number] ; address of number
 lea rcx, [scanf_fmt_2] ; address of " %lld"
 call scanf
 lea rbx, [person_array] ; we can't use fancy address calculation so manual it is
 mov rax, Person.size
 imul qword [number]
 sub rax, Person.size ; we force the user to start at 1, but we start counting at 0
 add rbx, rax
 lea rcx, [printf_fmt_2] ; address of "Enter a first_name: "
 call printf
 lea rdx, [rbx + Person.first_name] ; address of first name
 lea rcx, [scanf_fmt_1] ; address of " %s"
 call scanf
 lea rcx, [printf_fmt_3] ; address of "Enter a last_name: "
 call printf
 lea rdx, [rbx + Person.last_name] ; address of last name
 lea rcx, [scanf_fmt_1] ; address of " %s"
 call scanf
 lea rcx, [printf_fmt_4] ; address of "Enter an age: "
 call printf
 lea rdx, [rbx + Person.age] ; address of age
 lea rcx, [scanf_fmt_2] ; address of " %lld"
 call scanf
 lea rcx, [printf_fmt_5] ; address of "Enter a weight: "
 call printf
 lea rdx, [rbx + Person.weight] ; address of weight
 lea rcx, [scanf_fmt_2] ; address of " %lld"
 call scanf
 jmp .end
.reading:
 lea rcx, [printf_fmt_1] ; address of "Enter a number (1-5): "
 call printf
 lea rdx, [number] ; address of number
 lea rcx, [scanf_fmt_2] ; address of " %lld"
 call scanf
 lea rbx, [person_array] ; we can't use fancy address calculation so manual it is
 mov rax, Person.size
 imul qword [number]
 sub rax, Person.size ; we force the user to start at 1, but we start counting at 0
 add rbx, rax
 push qword [rbx + Person.weight] ; value of weight
 sub rsp, 32 ; personal shadow space because > 4 params
 mov r9, [rbx + Person.age] ; value of age
 lea r8, [rbx + Person.last_name] ; address of last name
 lea rdx, [rbx + Person.first_name] ; address of first name
 lea rcx, [printf_fmt_7] ; address of "Person (%s %s) is %d years old and weights %d"
 call printf
.end:
 jmp .continue_loop
 ret
Sᴀᴍ Onᴇᴌᴀ
29.6k16 gold badges45 silver badges203 bronze badges
asked Oct 10, 2018 at 17:46
\$\endgroup\$
2
  • \$\begingroup\$ @Mr. Vix don't edit the code. Feel free to point out bugs in an answer. Red more in the answers to this Meta post: _Should you edit someone else's code in a question? \$\endgroup\$ Commented Oct 10, 2018 at 23:21
  • \$\begingroup\$ I'm experiencing similar problems in accessing the elements of the array (Problem #2). I wonder if you were able to solve your issue? Thanks. \$\endgroup\$ Commented May 17, 2023 at 12:01

1 Answer 1

2
\$\begingroup\$
  1. Formatting: Is the overall structure of the assembly okay? Any improvements that could be made to the formatting and structure?

You have applied the nice tabular format to the source text. That's certainly fine! But I notice that both jmp .end and jmp .continue_loop are falling a bit short.
Also I would apply the same format to the structure:

STRUC Person
 .first_name: resb 50
 .last_name: resb 50
 .age: resq 1
 .weight: resq 1
 .size:
ENDSTRUC
  1. Accessing the elements of the array: For simple integer arrays, you can access them with [rax + rcx * (size of int)] where rax is the pointer to the start of the array and rcx is the position in the array we want to access. That didn't work for the Person array; I couldn't use [rax + rcx * Person.size]. So is there a better way to do that access?

Since the size of the Person structure is 108 bytes you couln't use that value for a scale value because the CPU limits the possibilities to the values 1, 2, 4, and 8.
For your manual address calculation you used imul qword [number] with RAX equal to the Person.size. This instruction is performing the widening multiplication that leaves its result in RDX:RAX. There's no real need to clobber the RDX register through using this 128-bit operation that Intel advices against (for efficiency reasons)! Just use the non-widening form of imul that has 3 operands: imul rax, [number], Person.size.
To account for the user-inputted number being 1-based, you then subtract Person.size one time. That is correct but you are wasting an instruction since you can combine this operation already in the lea that loads the address of the array: lea rbx, [person_array - Person.size]. Alternatively, you could opt to work 0-based internally but still allow the user to think 1-based. See the very last snippet in this answer.
Finally, re-arranging the code leaves us with next solution:

imul rax, [number], Person.size
lea rbx, [rax + person_array - Person.size]
  1. Other comments in general?

i. Alignment can be important for efficient access to the data. For the Person structure I would place those qword members first, followed by the byte members, and then pad the whole to obtain a size that's divisible by 8:

STRUC Person
 .age: resq 1
 .weight: resq 1
 .first_name: resb 50
 .last_name: resb 50
 .filler: resb 4
 .size:
ENDSTRUC

ii. Unlike it was in the 8086 era, x86 has no problem with the (limited) range of the conditional jumps. Therefore a construct like:

 je .writing
 jmp .reading
.writing:

is better replaced by:

 jne .reading
.writing:

iii. Currently the program duplicates a number of operations. You should place these in a subroutine like below:

; IN() OUT (rbx)
LocateRecord: 
 lea rcx, [printf_fmt_1] ; "Enter a number (1-5): "
 call printf
 
 lea rdx, [number]
 lea rcx, [scanf_fmt_2] ; " %lld"
 call scanf
 imul rax, [number], Person.size
 lea rbx, [rax + person_array - Person.size]
 ret

In your defense, apparantly you were "planning to move the request for the position out both the .writing and .reading blocks".


Working 0-based internally might be preferrable:

; IN() OUT (rbx)
LocateRecord: 
 lea rcx, [printf_fmt_1] ; "Enter a number (1-5): "
 call printf
 
 lea rdx, [number]
 lea rcx, [scanf_fmt_2] ; " %lld"
 call scanf
 dec qword [number] ; [1,5] -> [0,4]
 imul rax, [number], Person.size
 lea rbx, [rax + person_array]
 ret
answered Aug 25, 2024 at 15:05
\$\endgroup\$

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.