For sure this is a problem of experience. As we become more experienced with a subject, and start to internalize things, we start to more easily forget the areas in which we did not know, and how we came to know those things. Unless you are actively working with those that do not know but are learning, it is easy to forget.
So as, as mentioned by @SleeplessByte having a beginner (even if they already know “programming”) can be an invaluable source of what is missing, or what might be addressed.
There is an observation at the end of the instructions for this task:
Notice that `combining_function` may modify the values in registers you are using.
Make sure to save any variable you need in memory before calling the function.
Not directly related to your error, but I realized (from your thought process) that I should’ve made it clear that it is not possible to operate memory to memory, like so mov [a], [b]. Memory usually can be destination operand or source operand, but not both at the same time.
I’ll also create an entry on writing to memory. It is done in the same way, but it seems I forgot to make this explicit and didn’t even provide an example…
No, you need the address in order to write to it later, so you did right in saving rdi. The problem is that r8 may be modified by the called function. Registers are global and any one you can use can also be used by other functions. I’ve tried to hint at what should be done by saying you should save things in memory.
I think that right now all elements are in place, you only need to reread the instructions and specifically what you get as arguments and what each thing does.
That resulted in all 0s. Maybe I am not correctly writing to the location. I can test this by commenting out everything and directly writing to the incoming rdi
mov r8, [rel RED] ; store red in r8
mov [rdi], r8 ; write r8 to address
No dice: segmentation fault. Maybe I need to only get the 32bit values:
mov r8d, [rel RED] ; store red in r8
mov [rdi], r8d ; write r8 to address
ret
No dice: segmentation fault. Maybe I can try to directly write a constant.
mov dword [rdi], 42
No dice: segmentation fault. Perhaps with a full 32-bit value?
mov dword [rdi], 0xFF000000
No dice: segmentation fault.
From the tests of add_base_color, I know that this is the correct syntax to write a value to a location in memory:
mov dword [rel base_color], eax
Soooo Yeah. I can re-read the instructions for task 4 a gazillion times, but something isn’t adding up here What I am going to do now (do not tell me yet), is start over, and see what I missed!
brb!
EDIT
Okay, I seem to have removed an ret earlier, which made the segfaults happen sometimes. Now that this fixed, the following actually works:
; First store the address in memory for later
mov qword [rel color_dest], rdi
; Combine color
mov edi, dword [rel base_color] ; get current 32-bit base color
mov esi, dword [rsi] ; get passed in 32-bit secondary color
call combining_function
; Extract result
mov r8, [rel color_dest]
mov dword [r8], eax
ret
…which is pretty much what I had before. I also realised I could have used get_color_value and shuffle rdi around but in this case, I don’t think that makes the code clearer.
Takeaway
I do not think this final part was a mistake with the exercise.
I think it would be good to list something about writing to memory (like you mentioned)
I think it would be good to list something about memory being either left or right side (like you mentioned)
It seems lea is a shortcut for something? But I don’t know where I would use it.
I think the hints could have a pointer to “you can use variables to store memory”
I think the hints could have a pointer about rel for PIPE error
Overal: I enjoyed this again. I think how dereferences works isn’t completely clear after finishing this exercise, but I do feel cool about being able to solve it.
Right now it is only a way to get the address for a variable. It is more idiomatic to use lea dest, [var] than mov dest, var.
In the arrays concept, the full power of lea should be unleashed (or at least explained).
There is a hint for that in the General header in hints.md but for some reason no hint in this header appears in any of those concept exercises. I may need to copy them to each task’s section.
I’ll make a PR at some point later today or tomorrow morning with many changes based on what you’ve shared.
Once again, thank you very very much for your feedback!
oxe-b informed me via discord that this style of feedback is extremely helpful, so I will “learn in public” I suppose!
Arrays (Bird Watcher)
Before doing anything, I start my prework by adding empty function definitions and positional argument register comments, like I did for everything else. By now I remember that rdi is the first positional register as parameter, but forgot that that is dil for byte, so I had to open up Lasagna to check. I’ll get there!
I read that some have a 1 byte parameter (dil) and one has an 8 byte parameter (rdi) (because 8 * 8 = 64). I’ll now read the instructions.
On most platforms, this data is filled with zero by the OS at the start of the program.
Wondering if that means it’s good practice to zero-fill it before usage.
last_week_counts
Alrighto. I need to return an array with 8 bytes, with the last one zeroed. The initial value is 0, 2, 5, 3, 7, 8 and 4. I remember that Assembly is little-endian, meaning that the least significant byte comes first. So I think this is written in a way where I can make an array like so (pseudo):
[0 2 5 3 7 8 4 0]
I will do that using arr I suppose, as the example has that. I don’t understand why there is this:
Dunno what the point is of that last 0 because if I leave it off I have an array of length 7. Maybe it will become clear.
It returns a copy of last week’s counts as a 8-byte number
I need to copy this to rax. I wonder if I can “just” combine the bytes by giving back the start of the array. Given that rax will then start at that address, it will hold the next 8 bytes (rax is 64 bit). I don’t know if this means copy, but let’s try it.
lea rcx, [rel last_bird_counts]
mov rax, qword [rcx]
ret
The problem is that I cannot test this. The test passed before this code, and the test is still passing, so no clue if I did it right. I will move on and see if the next exercise will break my assumptions (tests are not passing for those)
I now think i know why I needed 8 elements. The last one being 0 means I can “just” convert it to a single 64-bit result.
current_week_counts
Sounds like the same exercise, except that I need to initialize it all to 0s and I need to return the second number which is “until what day we have counted”
I know that the array is made of bytes so I will need to multiply next_day_counted by the size of each element (8).
Let’s start with the bottom part
mov rdx, [rel day_index] ; find current index
lea rcx, [rel next_bird_counts] ; find address of first index
mov [rcx + 8*rdx], dil ; store incoming byte at location
add [rel next_bird_counts], 1 ; add one to the count
I get an error about the size being unknown. I think that makes sense because it doesn’t know that that 1 is a byte.
add byte [rel next_bird_counts], 1
It now compiles, but the tests complain.
save_count_first_day
Expected 5 Was 6
Ah. I think I made the same mistake again. I need to add ret to each function. Let me try that.
Now my previous test fails
last_week_default
Expected 0 Was 227
That is interesting. Why would it expect 0? I don’t really know what it is testing, so I don’t really have a good way to debug what’s wrong. In order to progress, I decide to look up the test and see if that helps me.
And what is today_count() ?
That is a function I have to implement. I haven’t done that yet, because that is task 4! Will that doesn’t really work.
I guess I will do that now so I can get hopefully to a passing test.
today_count
Alright. I can use day_index and subtract 1 to get the last counted day. It tells me I do not have to worry about -1.
mov rdx, [rel day_index] ; find current index
sub rdx, 1 ; look at last counted day
lea rcx, [rel next_bird_counts] ; find address of first index
mov al, byte [rcx + 8*rdx] ; retrieve byte at location
Yay. Now my Task 2 test is passing again. Let’s get back to the task at hand.
save_count (cont.)
I’ll again look at test and see where this 5/6 may be coming from.
5 may be expected as result of last_week_counts.
5 may be expected as crt.counts.
Without looking at the implementation, assume now that line 37 checks that last_week_counts() has not been changed, and that the actual issue is that I am (somehow) returning 6 instead of 5.
Well yeah sherlock. Ya done something stupid:
mov rdx, [rel day_index] ; find current index
lea rcx, [rel next_bird_counts] ; find address of first index
mov [rcx + 8*rdx], dil ; store incoming byte at location
+ add [rel next_bird_counts], 1 ; add one to the count
That doesn’t add one to the count. That adds one to the index. Dumb dumb dumb. Fixing it.
My guess is that that is a test that expects me that I implemented some of the code that I have not implemented yet, and it hits a loop or something like that. I will ignore this error and see if I can finish the exercise anyway…
Back to my save_count. I need to add the conditional to swap that thang. I think I will write it like this:
if day_index > 7, goto swap
else goto fill
Then swap can goto fill and everything stays the same logic.
mov rdx, [rel day_index] ; find current index
cmp rdx, 7 ; if day index >= 7
jae save_count.swap ; ..., then 7+ values are stored
jmp save_count.store
ret
Then I move my previous code to .store:
.store:
mov rdx, [rel day_index] ; find current index
lea rcx, [rel next_bird_counts] ; find address of first index
mov [rcx + 8*rdx], dil ; store incoming byte at location
add byte [rel day_index], 1 ; add one to the count
ret
And add .swap:
.swap:
call current_week_counts ; get current week count as single number
mov qword [rel last_bird_counts], rax ; store rax as last week count
mov qword [rel next_bird_counts], 0x0 ; clear array
mov byte [rel day_index], 0x0 ; clear day index
jmp save_count.store
ret
Now what I am uncertain of is if that mov actually clears the bytes (reset them).
I have not yet implemented update_today_count, so that’s next.
update_today_count
This reads as: get count of last day, add one, store it.
call today_count
add parameter
store it
call today_count
add al, 1
mov rdx, [rel day_index] ; find current index
sub rdx, 1 ; look at last counted day
lea rcx, [rel next_bird_counts] ; find address of first index
mov [rcx + 8*rdx], al ; store updated count
I’ve made a large PR that addresses part of those problems, but not others. I’ll look into the remaining issues now. It might take a while until it is reviewed, I’ll let you know!
I was, but that’s not the issue now. I will show you the code that is broken, and I also “fixed” that. I think it was me not implementing save_count completely and writing in “memory” where it shouldn’t.
I’ll show you in the message above how I wrote save_count. I am running into test-order issues again, but you’ll see (and you may already have fixed them)
; Bird Watcher (Arrays)
section .data
last_bird_counts db 0, 2, 5, 3, 7, 8, 4, 0
next_bird_counts db 0, 0, 0, 0, 0, 0, 0, 0
day_index dq 0
section .text
global last_week_counts
last_week_counts:
; This function takes no parameter
; It returns a copy of last week's counts as a 8-byte number
; At the start of the program, last week's counts are 0, 2, 5, 3, 7, 8 and 4
; The last byte of the return value is always zero
lea rcx, [rel last_bird_counts]
mov rax, qword [rcx]
ret
global current_week_counts
current_week_counts:
; This function takes no parameter
; It returns two values:
; - A copy of current week's counts as a 8-byte number.
; - The number of days already filled in the current week, as a 8-byte number.
; All days after the most recent one should have its corresponding byte zeroed-out in the output
; At the start of the program, there is no count for the current week
lea rcx, [rel next_bird_counts]
mov rax, qword [rcx]
lea rcx, [rel day_index]
mov rdx, qword [rcx]
ret
global save_count
save_count:
; This function takes as parameter the most recent count, as a 1-byte number ; di(l)
; It must save this value in a new entry for the current week
; If there is already 7 entries in the current week before the function is called, then:
; - The current week becomes the last week.
; - A new entry is added with the passed value in a new current week.
; The function has no return value
mov rdx, [rel day_index] ; store current index
lea rcx, [rel next_bird_counts] ; find address of first index
mov [rcx + 8*rdx], dil ; store incoming byte at location
add byte [rel day_index], 1 ; add one to the count
ret
global today_count
today_count:
; This function has no parameter
; It returns the most recent entry for the current week, as a 1-byte number
mov rdx, [rel day_index] ; store current index
sub rdx, 1 ; look at last counted day
lea rcx, [rel next_bird_counts] ; find address of first index
mov al, byte [rcx + 8*rdx] ; retrieve byte at location
ret
global update_today_count
update_today_count:
; This function takes as parameter a 1-byte number ; di(l)
; It adds this number to the most recent entry for the current week
; This function has no return value
ret
global update_week_counts
update_week_counts:
; This function takes as parameter a 8-byte number ; rdi
; Each byte in the input parameter, but the last, represents a day's count in the current week
; The last byte in the input parameter has no meaning and must be zeroed-out
; This function makes the following changes:
; - The current week becomes the last week.
; - The counts in the input parameter are fully inserted in the current week.
ret
%ifidn __OUTPUT_FORMAT__,elf64
section .note.GNU-stack noalloc noexec nowrite progbits
%endif
Previous post updated, now working on update_today_count
There were many changes in more than 40 files. If I didn’t miss any, all tests in all concept exercises now have messages like this one. I’ve also added tables to instructions like in lasagna and inventory management. And various changes in concept’s text.
I didn’t change the exercises’ instructions, though. So if anything is unclear, please let me know.
EDIT:
I’ll change the messages in those tests at some point later to show the expected result as a bitstring (instead of a decimal number). Thank you!
Oh exciting. I will go back into the exercises after I finish this one and inspect it!
Yeah I think maybe as little-endian bytes would help! Easier to read
Right now I don’t understand where I have fucked up:
mov rdx, [rel day_index] ; find current index
cmp rdx, 7 ; if day index >= 7
jae .swap
Doesn’t seem to matter if I use 7 or 8 or 6 or 12. I cannot get save_count to work, but qword [rel next_bird_counts] always returns 0 here (which I am then storing in last_bird_counts) or so it seems.
Total code thusfar:
; Bird Watcher (Arrays)
section .data
last_bird_counts db 0, 2, 5, 3, 7, 8, 4, 0
next_bird_counts db 0, 0, 0, 0, 0, 0, 0, 0
day_index dq 0
section .text
global last_week_counts
last_week_counts:
; This function takes no parameter
; It returns a copy of last week's counts as a 8-byte number
; At the start of the program, last week's counts are 0, 2, 5, 3, 7, 8 and 4
; The last byte of the return value is always zero
lea rcx, [rel last_bird_counts]
mov rax, qword [rcx]
ret
global current_week_counts
current_week_counts:
; This function takes no parameter
; It returns two values:
; - A copy of current week's counts as a 8-byte number.
; - The number of days already filled in the current week, as a 8-byte number.
; All days after the most recent one should have its corresponding byte zeroed-out in the output
; At the start of the program, there is no count for the current week
lea rcx, [rel next_bird_counts]
mov rax, qword [rcx]
lea rcx, [rel day_index]
mov rdx, qword [rcx]
ret
global save_count
save_count:
; This function takes as parameter the most recent count, as a 1-byte number ; di(l)
; It must save this value in a new entry for the current week
; If there is already 7 entries in the current week before the function is called, then:
; - The current week becomes the last week.
; - A new entry is added with the passed value in a new current week.
; The function has no return value
mov rdx, [rel day_index] ; find current index
cmp rdx, 7 ; if day index >= 7
jae save_count.swap ; ..., then 7+ values are stored
jmp save_count.store
ret
.swap:
mov r8, qword [rel next_bird_counts] ; get current week count as single number
mov qword [rel last_bird_counts], r8 ; store r8 as last week count
mov qword [rel next_bird_counts], 0x0 ; clear array
mov byte [rel day_index], 0x0 ; clear day index
jmp save_count.store
ret
.store:
mov rdx, [rel day_index] ; find current index
lea rcx, [rel next_bird_counts] ; find address of first index
mov [rcx + 8*rdx], dil ; store incoming byte at location
inc byte [rel day_index] ; add one to the count
ret
global today_count
today_count:
; This function has no parameter
; It returns the most recent entry for the current week, as a 1-byte number
mov rdx, [rel day_index] ; find current index
dec rdx ; look at last counted day
lea rcx, [rel next_bird_counts] ; find address of first index
mov al, byte [rcx + 8*rdx] ; retrieve byte at location
ret
global update_today_count
update_today_count:
; This function takes as parameter a 1-byte number ; di(l)
; It adds this number to the most recent entry for the current week
; This function has no return value
mov rdx, [rel day_index] ; find current index
dec rdx ; look at last counted day
lea rcx, [rel next_bird_counts] ; find address of first index
add [rcx + 8*rdx], dil ; store updated count
ret
global update_week_counts
update_week_counts:
; This function takes as parameter a 8-byte number ; rdi
; Each byte in the input parameter, but the last, represents a day's count in the current week
; The last byte in the input parameter has no meaning and must be zeroed-out
; This function makes the following changes:
; - The current week becomes the last week.
; - The counts in the input parameter are fully inserted in the current week.
ret
%ifidn __OUTPUT_FORMAT__,elf64
section .note.GNU-stack noalloc noexec nowrite progbits
%endif
Ah. eh. rdx is 8 bytes in size, so potentially a lot.
I made my index a byte, so I should have done 8*dl instead? Nah that won’t work either. I could make it a dq, so the reads are always right. But I don’t think that will solve it.
But I think what you’re saying is that I am not writing at the right location.