The x86-64 assembly syllabus is live

Note that indexes are always in bytes and starting at 0. How many bytes are there in 8*rdx?

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.

1 Like

The index is reset when it reaches 7, so the max index is 6. So 8*rdx varies from 0 to 48. But your array has only 8 bytes (and you use only 7 of those).

1 Like

Oh darn it. I don’t need 8*, because each thing is only taking one byte, not 8 bits (hence the 8).

Yeah okay. It’s obvious now. Is there a way to test for this behaviour earlier? I didn’t touch it because it felt correct :')

1 Like

Save some meta (like an analyzer or such), there’s no way. If the access is out of bounds, things might segfault. But they might not, especially because memory is contiguous and every variable can be accessed from another declared in a previous position (plus an offset large enough):

section .data
     var1 db 4
     var2 db 8 ; this can be accessed at var1 + 1

Maybe I can add some hint for something like that. I’ll think of something.

1 Like

Ah yes, and because I was consistent with my memory issue, I was consistently getting the same memory out of bounds. Okay, ignore me. Maybe a caution could help. I think that if you add it to this task, it would be good enough.


save_count (cont.)

With a bit of help from @oxe-b I realised I was using bits to index the memory offset instead of bytes. That won’t work! Removing the multiplication with 8 solved all my issues.

In the meanwhile I also updated the code and replaced add x, 1 with inc x and sub x, 1 with dec x. I don’t know if that is more idiomatic but it seemed like it!

update_week_counts

I think this is going to be easy.

  1. Copy swap
  2. Replace the 0x0 with the input parameter
  3. Forcefully unset the last byte
  4. Set the index to 7
    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], rdi  ; set array
    mov byte [rel next_bird_counts + 7], 0 ; clear last byte
    mov qword [rel day_index], 7           ; set day index

Takeaways

  • Better error messages help a lot. This change has already been made.
  • Showing the expected and actual numbers as bytes instead of a single integer would help.
  • The test order and task order didn’t line up (see “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.”)
  • Overall really fun exercise.
1 Like

I checked out The PR and now I see that the content has updated everywhere.

Yes, overall this is a huuuuge improvement.

I did spot this:
image

(and you still may want to bold italic low level those headings, also here:
image
).

Shall I do another one?

1 Like

Yes, please! I’ll keep rolling the PRs =)

1 Like

I’ve been wondering… what is:

%ifidn __OUTPUT_FORMAT__,elf64
section .note.GNU-stack noalloc noexec nowrite progbits
%endif

Perhaps could have a comment in lagana to explain and leave alone.

Bit-manipulation (Secrets)

Ooooh fun. I am a big fan of this and most of my solutions to secrets accross Exercism use bitwise operators. I start again with the work to define them functions so I can run the tests. I do notice some inconsistencies in the sub comments. Arguments are not listed as list, and the copy is slightly different in most functions.

image

Let’s get into it!

I read about immediate. I guess this is the “literal” we know from other languages. I do not recall if this was mentioned in an earlier concept.

extract_higher_bits

I am wondering what the canonical / idiomatic way to do this is. My intuition says:

  • get the parameter rdi (but then the 16bit size)
  • and with 0b1111111100000000

I see in the tests that I can write it with underscores:

0b1111_1111_0000_0000

About the and operator I read:

Most of them take two operands, perform a bitwise operation on both and store the result in the destination operand. The exception is not, which takes just one destination operand.

Yeah okay but which one is the destination operand? I would assume the first because add x, y has x as destination. I recall from computer science (which I studied long ago) that this was the difference between the two types. One had source before dest and one had dest before source.

Anyway. It’s easy to test:

mov ax, 0xFF
and di, ax

if this error with the result being 255 and the inverse errors with non-255, then and ax, di means and dest, src.

mov ax, 0xF0
and ax, di

Unfortunately this gives me Expected 164 Was 192.. I think that’s because I am now setting the 16bit register and it expects 8bits, which are ah/al. I don’t know how to do the conversion, but I re-read lasagna and see:

When using less than 64-bits, the bits accessed are usually from the lower portion of the register. The exception to this rule are ah , bh , ch and dh , which access the upper 8-bits from the 16-bits portion of the register.

So that means that if I can write 0000_0000_value to the 16bit register, and the test uses al, then I am golden.

0 - 8 9 - 15
di :ballot_box_with_check: :wastebasket:
ax 0 :ballot_box_with_check:

What I need to accomplish is move the bits from di to the right, fill with zeros, and return that.

mov ax, di
shr ax, 8

extract_lower_bits

I think I can use the same trick here:

0 - 8 9 - 15
di :wastebasket: :ballot_box_with_check:
ax 0 :ballot_box_with_check:

So either I and with 0x0F, or I shift left, then call extract_higher_bits. Since I don’t know yet how to make the and work here, I take the second approach:

shl di, 8
call extract_higher_bits

extract_redundant_bits

I think what I need to do is:

0 - 8 9 - 15
di :question: :question:
ax 0 0-8 & 9-15

I already know how to get the higher and lower bits, and I know how to get only the 8 bits so I am in the right size right away (al instead of ax)

call extract_higher_bits
mov bl, al

call extract_lower_bits
and al, bl

Yay.

set_message_bits

From the instructions it’s not completely clear if high or low is the mask or message. However, the goal is: return 1 if mask is 1, otherwise keep the original bit in the message. That means: if either bit is 1, have the bit be 1, if neither bit is 1, have the bit be 0. That sounds like or

1100 0100 1100 0101

1100 0100
1100 0101
1100 0101 OR

1110 0101 EXAMPLE

Yeah I don’t think the example is correct. I am just going to try it.

call extract_higher_bits
mov bl, al

call extract_lower_bits
or al, bl

It worked! @oxe-b you probably want to update the example.

rotate_private_key

I believe I should start with extracting the redundant bits, and then:

  • count the number of 1s
  • take the private key
  • rotate it left by that count

I’ll need popcnt which only works on 16-bit values;
I’ll need rol which only works with the second value being cl (or a literal).

That means I must zero-extend the redundant bits answer:

call extract_redundant_bits    ; find redundant bits
movzx ax, al                   ; zero-extend answer to 16bits
popcnt cx, ax                  ; count number of 1s

The number in cx must live in cl as well, because there are only 16 bits to count. I don’t quite understand why this operation only takes the 16-bit arguments on both sides, but that’s besides the point.

At this moment I can still run the tests. Let me try to read the private key

section .data
    PRIVATE_KEY equ 0b1011_0011_0011_1100

If it turns out I never update this, it can become .odata.

Then I try tor move it into the result (and then rol the result):

mov ax, word [PRIVATE_KEY]     ; get private key
rol ax, cl                     ; rotate left by stored count
ret

That gives me a segmentation fault. Meh.

mov ax, 0b1011_0011_0011_1100  ; get private key
rol ax, cl                     ; rotate left by stored count
ret

This passes the tests. Okay. I supposed that LABEL equ value doesn’t give me memory addresses to play with, and the dq things do. I think I forgot what the concepts say about that, but I can try to inline the label directly:

mov ax, PRIVATE_KEY            ; get private key
rol ax, cl                     ; rotate left by stored count
ret

Superb. Two more to go!

format_private_key

I think this is called after the key is already rotated, so no need to rotate the key.

  • Isolate the lowest 8-bit portion of the rotated private key, which is the base value. That means extract_lowest_bits
  • Isolate the highest 8-bit portion of the rotated private key, which is a mask to be applied to the base value. That means extract_highest_bits
  • Flip set bits in the base value that are also set in the mask. That is exclusive or
  • Flip all bits in the result.: That is not
call extract_higher_bits       ; get the mask
mov bl, al
call extract_lower_bits        ; get the base value
    
xor al, bl                     ; flip what's set in both
not al                         ; flip it

That does not pass any tests, but I am pretty certain it’s correct.

Perhaps my interpretation that it was already rotated (please add a note to the instructions) is wrong. Easy peasy lemon squeezy:

call rotate_private_key
mov di, ax

One more to go :dancer:t4:

decrypt_message

I think it’s a bit hidden that encoding both the message and a mask is the input argument. I found the same text for other instructions, so I know I need to pass it into format_private_key, but I do think the stub can be improved to clearly indicate what is being passed in where.

I have to start with generating that private key, because that needs to be returned in the result.

Now wondering: are the registers for rdi preserved across calls or no? In other words, if I am in function A, call function B which changes rdi, does it get changes in function A? I remember reading something about preserving but I do not recall. Let me look it up.

Some of those registers must be preserved across function calls: rbp, rsp, rbx, r12, r13, r14 and r15. Failing to preserve them may lead to an error or to undefined behaviour.

The others are not preserved and may be used freely: rax, rcx, rdx, rdi, rsi, r8, r9, r10 and r11.

That doesn’t answer my question. I’ll play it save and:

  • explicitly store rdi
  • explicitly set rdi before a call if needed
mov r8w, di                   ; store original input
call format_private_key       ; get private key
mov r9b, al                   ; store private key

The instructions are not really clear on what needs to be happening, but reading back to set_message_bits I can see that that takes a message-mask combination and “set the relevant bits”. Let’s use that:

mov di, r8w                   ; prepare set bits
call set_message_bits          

I do not understand how I am decrypting the message, or what that means, but if I ignore that gap, I take the instructions at face value and prepare the following:

  • expand the message with 0s on the left
  • expand the private key with 0s on the right
  • OR both, values to get the result

The first I can do with movzx ax, al.
The second I can do with shl <>, 8.

; Combine the two
movzx ax, al                  ; zero-fill output on the left (high)
shl r9w, 8                    ; shift left private key (left fill)
or ax, r9w                    ; combine

And that concludes the exercise!

Takeaways

By far the exercise where I got stuck the least. A few things you already know about, and some more:

  • Stub could be more consistent with the others, more explicit about the contents of the arguments
  • It is (still) unclear to me, how you can “make smaller” a register, but I have a feeling it’s clearing the higher bits and that’s it. It was easier for me to use shift left and right in the first two tasks. Don’t know if that was intended :hugs:
  • The equ thing you know about
  • Final exercise could explicitly state that you don’t need to “understand decryption” or something similar
  • Fixing the example for OR

How did I do?

1 Like

This is for the assembler to add some parameters in the executable, so that the OS separates stack space in a certain way. The bottom line is that it is a security measure.

This was not clear at the beginning, but after you’ve pointed it out, I added an observation for it in the basics concept and an entry in the integers concept.

This is from the basics concept, it is instruction dest, src for a two-operand instruction. At least in the syntax we use in the track (Intel’s).

But I think that I should discuss this a bit more in the integers concept when I talk about one-operand and three-operand imul.

Yes, I think you’re right. I’ll look into it.

Thank you very much!

1 Like

Another thing to explain, this time in the memory concept.

Yes, equ is for assembler-time constants. They are just placeholders for the assembler (basically, you are creating a readable version of a literal). So you can use them whenever you’d use a literal and they do not occupy space in memory.

1 Like

In case someone wants to read my solutions, I left all the commentary in. I do think mine are more readible than most XD.

I have time for another one, sooooo Mixed Juices!… here I come :confetti_ball:

2 Likes

Loops (Mixed Juices)

After preparing the stub I can run the tests. A few things spring to mind:

  • Sometimes an array is passed in which means a memory address is passed in, which is 64bit (rdi / rsi etc.).
  • Sometimes a 32-bit number is passed in (edi / esi)
  • It returns 32-bit numbers (eax)

Reading the content now.

Found a missing ref here.

Whilst reading I saw that once again rcx is special (rotate also had this). It seems that so far built-ins use rcx whenever they need outside config / state.

Finally I think that loop decrements for me, does not do the comparison, but does exit based on the rflag. I’ll see what I can do with that.

time_to_make_juice

In JavaScript/TypeScript I would have done this with an object lookup or case statement. With 8 items, I think I’ll store this in an array instead, and then lookup the time in the array

id name time array index
1 Pure Strawberry Joy 1 0
2 Energizer 3 1
3 Green Garden 3 2
4 Tropical Island 4 3
5 All or Nothing 5 4
6 Feel Good 4 5
7 Today’s Special 7 6
8 Client’s Choice 10 7

That gives me

section .odata
    time_to_juice db 1, 3, 3, 4, 5, 4, 7, 10

Getting to the correct memory address I can use lea plus math

lea r8, [rel time_to_juice]    ; get memory of first item
add r8, rdi                    ; move to next byte * id
dec r8                         ; ID is 1-indexed, array is 0-indexed

Finally I can get the time-to-juice and ensure zero-fill

mov al, byte [r8]              ; retrieve time to juice
movzx eax, al                  ; ensure 32-bit number

That passes the first 8 tests.

time_to_prepare

I need to map-reduce the drinks to the total juicing time, effectively summing all the time to juice. I do not know how to mutate the array length (probably not really a thing), so recursive will be hard. As this is the loop exercise, probably need to loop.

time_to_prepare({2, 3, 8}, 3)

This probably means I need to loop 3 times (so set rcx) and then for the index 0, 1, 2 collect the time to juice and sum it. My guess is that, because the time to prepare is order-independent, I can use the rcx index to my advantage and count from the right, or I need to math the input variable - loop iterations left. I could also store the index register and increment it by 1 each iteration.

Many options.

mov ecx, esi                     ; loop this many times
mov r9d, 0                       ; store the sum (0 initially)
lea r10, [rdi]                   ; store the array start (input ordered juices)

Now if I understood correctly, I can now add a local (jump) label, and it will be able to GOTO that label when the loop instruction is hit, until ecx is 0.

Getting the ID of the current juice is using the effective address thing where I take the base address, add size * index +/- displacement.

iteration rcx array index expected rel
0 3 2 base + 4 * 3 - 4
1 2 1 base + 4 * 2 - 4
2 1 0 base + 4 * 1 - 4

After iteration 2, rcx will hit 0 and it will not jump back to the top.

.juice:
    mov edi, dword [r10 + 4*rcx - 4] ; get the ID of the current juice
    
    call time_to_make_juice          ; get the time to juice
    add r9d, eax                     ; add to sum
    loop .juice                      ; repeat until rcx is 0
    
    mov eax, r9d                     ; return the sum
    ret

Tests pass, so I continue


limes_to_cut

wedges […] each represented by a 8-bit number

I see 'characters' in the example input. I wonder if those are turned into an 8-bit number. I have not learnt about characters yet. I think the solution for that is extern LIME_SMALL etc, but alas. I’ll try to equ the character and see what happens.

          LIME_S equ 'S'
          LIME_M equ 'M'
          LIME_L equ 'L'

This compiles, so I’ll stick with it. I don’t know how to make mappings or anything like that, so I’l pick the easy route and define lime_wedges that takes a lime and returns a 32-bit number for the amount of wedges.

No clue if equ makes these 32-bits instead of 8-bits, but I’ll know very fast if this works.

lime_wedges:
    ; - Takes one input parameter: 8-bit number        ; dil
    ; Returns the number of wedges as 32-bit number

    cmp dil, LIME_S
    je .small
    cmp dil, LIME_M
    je .medium

    mov eax, 10
    ret
.small:
    mov eax, 6
    ret
.medium:
    mov eax, 8
    ret

I can then implement limes_to_cut to quickly test my function:

lea r10, [rsi]        ; get the memory address of first lime
mov dil, byte [r10]   ; deref first lime character into arg1
call lime_wedges

If this works, I will see something like

Expected 1 Was 6. Number of wedges needed: 6. Limes: ‘S’.

If it doesn’t work I will get an error, or “Was 10”.

Great. Now I can build the limes_to_cut function. I’ll set-up the structure first, then implement it.

    mov ecx, edx     ; loop this many times (at most amount of lime times)
    
    lea r8, [rsi]    ; store index of first lime to cut
    mov r9b, 0       ; track lime index
    mov r10d, 0      ; track cut count
    mov r11d, edi    ; track count still needed

    cmp ecx, 0       ; if there is nothing to cut, break
    je .break
   
.loop:
    cmp r11d, 0      ; if there is nothing left to cut, break
    jbe .break
    
    ; TODO: cut lime, add wedges to count
    ; increment lime index
    loop .loop
.break:
    mov eax, r10d    ; return the count
    ret

It still compiles and runs, so it’s probably correct.

I now realise I do not need to track both cut count and index. They are the same thing.

    mov ecx, edx     ; loop this many times (at most amount of lime times)
    
    lea r8, [rsi]    ; store index of first lime to cut
    mov r9, 0        ; track lime index (cut count)
    mov r10d, edi    ; track count still needed

    cmp ecx, 0       ; if there is nothing to cut, break
    je .break
    
.loop:
    cmp r10d, 0      ; if there is nothing left to cut, break
    jle .break

    mov dil, byte [r8 + 1*r9]    ; get lime at index
    call lime_wedges             ; get wedges for lime
    sub r10d, eax                ; subtract cut wedges

    inc r9b          ; move lime index +1
    loop .loop
.break:
    mov eax, r9d     ; return the count (lime index)
    ret

I think I did that right!


remaining_orders

Looks like more of the same (which is good).

I should loop as long as there is time left in shift;
I should loop at most the length of the array (which I do not know);
I should loop until the time in the shift is done;
I should return how many juices have been made;

I probably can use the jmp with exit condition to make this loop work:

    lea r8, [rsi]    ; store index of first drink to make
    mov r9, 0        ; track drink index
    mov r10d, edi    ; track time left

.loop:
   ; TODO prepare a drink
   ; TODO subtract from r10
   
   cmp r10d, 0
   jle .handover
   
   jump .loop

.handover:
    mov eax, r9d     ; return the count (drink index)
    ret

Unfortunately time_to_make_juice uses r8, so I’ll shift everything by 1. (r9, r10, r11)

I should not run this code, because there is nothing to decrease r10d right now, so it won’t work.

.loop:
    mov edi, dword [r9 + 4*r10]   ; prepare juicing
    call time_to_make_juice
    
    sub r11d, eax    ; subtract time passed
    inc r10          ; move drink index +1
   
    cmp r11d, 0      ; if out of time
    jle .handover    ; ...handover
   
    jmp .loop

.handover:
    mov eax, r10d    ; return the count (drink index)
    ret

Yoooooooo I got this on the first try


Takeaways

  • The character literal thing was weird
  • I think that’s it!
2 Likes

Yeah, this was my bad. This concept was planned to depend on strings, but things changed and I forgot to update this task. I’ll change this to use constant values.

1 Like

Despite that, I didn’t need any help! So the concept exercise ported really well.

1 Like

Very well!

I’ve noticed that you are getting less stuck as you advance. This is in part something I expected, because one of the hardest thing in assembly is how “strange” things are in the beginning, without named arguments and with lots of manual stuff handling bytes and such. Once you get the hang of this, most things follow more or less.

About your question on “reducing” a register: you don’t usually need to do it. A register is a 64-bit memory slot in the CPU. The 32-bit, 16-bit, 8-bit and any byte in between are all inside this slot. When you return a 8-bit value, only the first byte of rax is used, the rest doesn’t matter. Same thing if you have, say, a ecx and do a mov dl, cl, you copy the first byte. The logic is the same for other sizes.

Even though we’ve learned that in a 32-bit positive number the upper bits are cleared, you shouldn’t think of registers are numbers. Registers are bytes, memory is bytes, even code is bytes. Integers and everything else are just abstractions/views over bytes, that are defined according to the rules we set. This is an overarching theme on the syllabus.

My aim is that once you go solve the practice exercises, after a while, you are able to create data structures such as queues, pqueues, etc by saying “those 19 bytes are a node, with one byte integer with meaning X, one 8-byte address with meaning Y and 10-bytes stuff that sometimes has meaning Z and sometimes has meaning K”. And then you can reinterpret things as you see fit.

1 Like

By the way, thank you again for all the feedback! I’ll make a second large PR (less large than the first) tonight or tomorrow to address lots of things based on what you shared.

You’ve already reached half of the syllabus! With what you know, you are able to solve lots of practice exercises already if you have some basic knowledge of C types.

1 Like

Yeah that’s where I hope to get.

I also hope to learn more about when to use internal memory, and when to keep abusing the registers etc.


p.s.

image

2 Likes

Yeah, but I do need to clear it, because the tests often check the entire thing :stuck_out_tongue: . I guess that’s expected!

I also want to know if it’s idiomatic to clear things often or not. I don’t see it in your or kieraville’s solutions, but my feeling is it could prevent a lot of bugs! I suppose it does take CPU cycles…

2 Likes

If you need, say, 4 bytes and you have 1 or 2 bytes, then you may need to clear upper bits if they are to be interpreter as integers (or set them for negative numbers).

This happens exactly because each byte is its own thing and the calling function (which called your code) only guarantees that you get the bytes that you get. The rest have undefined values. So, you need to manually ensure the other bytes have the value you need in order to interpret the sequence in the way you want.

To reduce, this is usually not necessary because if the calling function expects the result to fit, say, 1 byte, then it can not expect any result in any byte after the first. And to have the value in the entire sequence, you need to have the partial value in each byte, so things are already set.

As for what is idiomatic, it depends on your use case. There are some situations when it is good to clear bytes for security reasons, to “hide” data, but other than that, if it fits the contract between calling and called function, without undefined behavior, then it’s usually right.

The most common and “idiomatic” way to clear a register is using xor with itself. For example, xor rax, rax.

2 Likes