Διαδρομές
/
x86-64 Assembly
x86-64 Assembly
/
Ασκήσεις
/
Διάταξη περιοδικού
Διάταξη περιοδικού

Διάταξη περιοδικού

Άσκηση εκμάθησης

Εισαγωγή

Μακροεντολές

Ο κώδικας assembly που γράφεται στο χέρι είναι γεμάτος επαναλήψεις. Η ίδια σύντομη ακολουθία εντολών εμφανίζεται ξανά και ξανά, με μικρές παραλλαγές, και υπάρχουν λίγες αφαιρέσεις που θα μείωναν τον επαναλαμβανόμενο κώδικα. Επιπλέον, ο προγραμματισμός σε assembly απαιτεί τη διαχείριση τιμών χωρίς προφανή σημασιολογία. Δεν υπάρχουν ονομασμένες παράμετροι και οι περισσότερες λεπτομέρειες του υποκείμενου μηχανισμού πρέπει να αντιμετωπίζονται χειροκίνητα από τον προγραμματιστή.

Ο NASM, ο assembler που χρησιμοποιεί το track, προσφέρει κάποια εργαλεία που μπορούν να βοηθήσουν τον προγραμματιστή και με τα δύο προβλήματα. Αυτά τα εργαλεία εκτελούνται στον χρόνο assembly, προτού φτάσει οποιαδήποτε εντολή στον επεξεργαστή. Καθοδηγούν τον assembler στο πώς να ξαναγράψει τον πηγαίο κώδικα προτού καν αυτός μεταφραστεί σε κώδικα μηχανής.

Το πιο σημαντικό από αυτά τα εργαλεία είναι οι μακροεντολές, που μπορούν να θεωρηθούν σαν μια συνταγή για τον assembler. Επεκτείνονται στο σημείο όπου χρησιμοποιούνται, σαν ο κώδικας να είχε γραφτεί εκεί στο χέρι.

Οι μακροεντολές μπορούν να πάρουν έναν αριθμό από literal tokens ως παραμέτρους, γεγονός που τις κάνει επαναχρησιμοποιήσιμες σε διαφορετικά πλαίσια. Ωστόσο, δεν είναι συναρτήσεις: δεν υπάρχει call, ούτε ret, ούτε μεταφορά του ελέγχου σε άλλο σημείο του κώδικα.

Μακροεντολές μίας γραμμής

Ο πιο απλός ορισμός δίνει ένα όνομα σε μια τιμή ή σε μια σύντομη έκφραση με το %define:

%define WIDTH 8
%define BUFFER_SIZE (WIDTH * 16)

Κάθε επόμενη εμφάνιση του WIDTH αντικαθίσταται από το 8 πριν από το assembly. Η αντικατάσταση είναι κειμενική, οπότε το όνομα υποκαθίσταται οπουδήποτε εμφανίζεται, είτε σε έναν τελεστέο είτε μέσα σε άλλον ορισμό.

Note

Για αριθμητικές σταθερές, προτίμησε το equ αντί για το %define.

Μια μακροεντολή μίας γραμμής που δηλώνεται με %define επεκτείνεται μόνο όταν χρησιμοποιείται και μπορεί να της δοθεί νέα τιμή ή να καταργηθεί. Αντιθέτως, μια σταθερά equ αξιολογείται τη στιγμή του ορισμού της και δεν τροποποιείται ποτέ.

Ένα %define μπορεί επίσης να δεχτεί ονομασμένες παραμέτρους, συμπεριφερόμενο σαν μια μικρή inline συνάρτηση:

%define double(x) ((x) + (x))

    mov eax, double(WIDTH)   ; assembled as mov eax, ((8) + (8))
Caution

Τύλιξε σε παρενθέσεις και ολόκληρο το σώμα και κάθε παράμετρο. Με το %define square(x) x*x, η κλήση square(1+1) επεκτείνεται σε 1+1*1+1, που είναι 3. Γραμμένο ως %define square(x) ((x)*(x)), η ίδια κλήση επεκτείνεται σε ((1+1)*(1+1)), που είναι 4.

Τα ονόματα που ορίζονται με %define διακρίνουν πεζά και κεφαλαία γράμματα. Η παραλλαγή %idefine κάνει το όνομα να μη διακρίνει πεζά και κεφαλαία, αν αυτό είναι επιθυμητό.

Οι μακροεντολές μίας γραμμής είναι χρήσιμες για να δίνουν νόημα σε διάφορα σημεία του κώδικα. Αν χρησιμοποιηθούν με προσοχή, αυτό βελτιώνει την αναγνωσιμότητα και μειώνει την πιθανότητα τυπογραφικών λαθών. Για παράδειγμα, ένας καταχωρητής που έχει συγκεκριμένο σκοπό μέσα σε μια συνάρτηση μπορεί να οριστεί με βάση τον σκοπό του:

%define COUNTER rcx
    ...
.loop:
    ...
    dec COUNTER
    jnz .loop
    ...

Μακροεντολές πολλών γραμμών

Μια ολόκληρη ακολουθία κώδικα, με οποιονδήποτε αριθμό γραμμών, συλλαμβάνεται με το %macro και κλείνει με το %endmacro. Ένας αριθμός μετά το όνομα δείχνει πόσα ορίσματα δέχεται η μακροεντολή, που μπορεί να είναι μηδέν. Όταν περνούν ορίσματα, χωρίζονται με κόμμα (,):

%macro example_macro 0
...
%endmacro

Τα ορίσματα στις μακροεντολές πολλών γραμμών δεν έχουν ονόματα. Αναφέρονται ως %1, %2 κ.ο.κ., με τη σειρά που περνιούνται:

; Multiplies two values, returning the result in the first argument
%macro mult_two 2        ; %1 = accumulator, %2 = value
    imul %1, %2
%endmacro

    mult_two eax, ecx    ; multiplies eax and ecx and returns the result into eax
    mult_two r8, r9      ; multiplies r8 and r9 and returns the result into r8

Σημείωσε ότι τα ορίσματα πρέπει να είναι έγκυρη σύνταξη για τις πράξεις που εκτελούνται στο σώμα της μακροεντολής. Εδώ τα mult_two eax, ecx και mult_two r8, r9 συναρμολογούνται και τα δύο, αλλά το mult_two eax, cl δεν θα συναρμολογούνταν, αφού το imul eax, cl δεν ορίζεται.

Ετικέτες μέσα σε μια μακροεντολή

Μια μακροεντολή που ορίζει μια συνηθισμένη ετικέτα σπάει τη στιγμή που θα χρησιμοποιηθεί δύο φορές, γιατί τότε η ετικέτα θα οριστεί κι αυτή δύο φορές. Για να χρησιμοποιήσεις ετικέτες μέσα σε μια μακροεντολή που μπορεί να κληθεί περισσότερες από μία φορές, είναι απαραίτητο να βάλεις το πρόθεμα %% σε μια τέτοια ετικέτα. Αυτό δίνει εντολή στον assembler να δώσει σε αυτή την ετικέτα ένα διαφορετικό, μοναδικό όνομα σε κάθε επέκταση:

; clobbers rcx
%macro clear_n 2         ; %1 = base address, %2 = count of dwords to clear
    xor ecx, ecx
%%loop:
    mov dword [%1 + rcx*4], 0
    inc ecx
    cmp ecx, %2          ; %2 is compared with ecx: it is an immediate or a 32-bit operand
    jb %%loop
%endmacro

    clear_n rdi, 4       ; gets its own %%loop
    clear_n rsi, 8       ; a different %%loop, no clash

Επανάληψη στον χρόνο assembly

Μπορείς να επαναλάβεις ένα μπλοκ κώδικα μίας γραμμής χρησιμοποιώντας το times. Αυτό είναι ιδιαίτερα χρήσιμο όταν δηλώνεις επαναλαμβανόμενες τιμές στη μνήμη:

section .data
negative_block: times 16 db -1
; this assembles to 16 db -1 in sequence, i.e., an array of 16 bytes, all equal to -1

Για επανάληψη κώδικα πολλών γραμμών, μπορούμε να χρησιμοποιήσουμε το %rep:

%rep 2          ; number of repetitions: 2
    add rcx, 32
    mov dword [rsi + rcx], 0
%endrep

Σημείωσε ότι ο αριθμός των επαναλήψεων καθορίζεται αμέσως μετά το %rep και υπάρχει ένα %endrep για να κλείνει το επαναλαμβανόμενο μπλοκ.

Συνηθίζεται να χρησιμοποιείται ένας μετρητής χρόνου assembly μέσα σε ένα μπλοκ %rep, όπως ακριβώς ένας βρόχος χρόνου εκτέλεσης χρησιμοποιεί έναν μετρητή. Ενώ μια ακέραια σταθερά χρόνου assembly μπορεί να δηλωθεί με equ, για ακέραιες μεταβλητές χρόνου assembly χρησιμοποιούμε αντ' αυτού το %assign. Σε αντίθεση με το %define, το %assign αξιολογείται αμέσως σε έναν αριθμό και μπορεί να του ανατεθεί νέα τιμή:

    xor eax, eax
%assign i 0
%rep 4
    add eax, i           ; add 0, then 1, then 2, then 3
%assign i i + 1
%endrep

Αφού εκτελεστεί ο προεπεξεργαστής, ο βρόχος έχει εξαφανιστεί και απομένουν τέσσερις εντολές add, με τις άμεσες τιμές 0, 1, 2 και 3 ενσωματωμένες. Αυτό είναι ξεδίπλωμα βρόχου που κάνει ο assembler: χωρίς καταχωρητή μετρητή και χωρίς διακλάδωση, μόνο ευθύγραμμος κώδικας.

Υπό συνθήκη assembly

Ο προεπεξεργαστής έχει στη διάθεσή του διάφορες συνθήκες. Επιλέγουν ανάμεσα σε διάφορες διαδρομές, επεκτείνοντας το σώμα τους επιτόπου, αλλά μόνο όταν η συνθήκη τους είναι αληθής.

Τα %if ... %elif ... %else ... %endif επιλέγουν μια διακλάδωση με βάση έναν αριθμητικό έλεγχο:

%if WIDTH > 4
    mov eax, 2
%elif WIDTH > 2
    mov eax, 1
%else
    mov eax, 0
%endif

Χωρισμός σε αρχεία

Οι μακροεντολές συχνά διατηρούνται σε δικό τους αρχείο και εισάγονται σε κάθε πηγαίο αρχείο που τις χρειάζεται με το %include:

%include "patterns.mac"

Το αρχείο που ονομάζεται εισάγεται αυτούσιο σε εκείνο το σημείο, πριν από το assembly, ακριβώς σαν να είχε πληκτρολογηθεί το περιεχόμενό του εκεί. Έτσι οι ορισμοί μένουν σε ένα σημείο και πολλά πηγαία αρχεία μπορούν να τους μοιράζονται.

Αν το αρχείο μακροεντολών μπορεί να συναρμολογηθεί και μόνο του, θα πρέπει να έχει επέκταση αρχείου .asm. Σημείωσε ότι οι μακροεντολές δεν έχουν σύνδεση. Ακόμα κι αν το αρχείο μακροεντολών συναρμολογηθεί, οι μακροεντολές του είναι ορατές μόνο σε ένα άλλο αρχείο που το εισάγει με %include.

Διαφορετικά, ένα αρχείο που προορίζεται μόνο για %include μπορεί να έχει οποιαδήποτε επέκταση αρχείου. Η χρήση είτε του .asm είτε του .mac είναι μια συνηθισμένη σύμβαση.

Οδηγίες

Συντηρείς τη βιβλιοθήκη μακροεντολών πάνω στην οποία είναι χτισμένη η μηχανή στοιχειοθεσίας ενός περιοδικού. Η ίδια η μηχανή, οι συναρτήσεις που σελιδοποιούν κάθε σελίδα, είναι σταθερή και δεν τη γράφεις εσύ. Αυτό που γράφεις είναι ο μηχανισμός του χρόνου συναρμολόγησης από τον οποίο είναι χτισμένη.

Αυτή η άσκηση δεν απαιτεί να γράψεις καμία συνάρτηση, μόνο μακροεντολές. Στη συνέχεια, αυτές οι μακροεντολές συμπεριλαμβάνονται και καλούνται σε συναρτήσεις που έχουν ήδη προκαθοριστεί σε άλλο αρχείο.

Έχεις τέσσερις εργασίες.

Caution

Κάθε μακροεντολή καλείται με το ακριβές όνομα και την ακριβή σειρά ορισμάτων που δίνει η εργασία της, οπότε γράψε τις ακριβώς. Αλλιώς το αρχείο δεν θα συναρμολογηθεί.

1. Δώσε ονόματα στις σταθερές της μορφής

Η μορφή του περιοδικού είναι μια χούφτα σταθεροί αριθμοί, και το υπόλοιπο της διάταξης αναφέρεται σε αυτούς μόνο με το όνομά τους. Θα πρέπει να δώσεις σε καθέναν ένα ευανάγνωστο όνομα στον χρόνο συναρμολόγησης:

  • COLUMN_COUNT είναι 4
  • COLUMN_STRIDE είναι 18
  • SPACE είναι 0x20, το κενό byte
  • RULE είναι 0x3D, το byte =
  • RULE_WIDTH είναι 4
  • PLAIN είναι 0x2E, το byte .
  • RULED είναι 0x2D, το byte -
  • BOXED είναι 0x23, το byte #

Η μηχανή τα αναφέρει με τη σειρά παραπάνω.

out = {4, 18, 32, 61, 4, 46, 45, 35} // the eight named values, in order

2. Γράψε τη μακροεντολή fill_run

Η μηχανή γεμίζει με κενά ένα δοκίμιο και τοποθετεί τις γραμμές της επικεφαλίδας σφραγίζοντας μια σειρά από ένα μόνο byte. Αποτύπωσε αυτό το βήμα ως μακροεντολή πολλαπλών γραμμών που η μηχανή μπορεί να καλέσει όπου χρειάζεται να γεμίσει μια σειρά.

Πρώτα, όρισε ένα ευανάγνωστο όνομα COUNTER για τον καταχωρητή rcx. Έπειτα, όρισε τη μακροεντολή fill_run που θα γεμίζει έναν πίνακα με N αντίγραφα ενός byte. Δέχεται τρία ορίσματα με αυτή τη σειρά:

  1. τη διεύθυνση προορισμού, που περνιέται σε έναν καταχωρητή γενικού σκοπού 64 bit.
  2. το πλήθος των byte (N) που θα γραφτούν, που περνιέται σε έναν καταχωρητή γενικού σκοπού 64 bit ή ως άμεση τιμή.
  3. το byte που θα γραφτεί, που περνιέται ως άμεση τιμή.
fill_run rdi, rax, SPACE
fill_run rdx, RULE_WIDTH, RULE

Η μακροεντολή fill_run μπορεί να χαλάσει ελεύθερα τον COUNTER, αλλά κανέναν άλλο καταχωρητή.

Σημείωσε ότι το πλήθος των byte μπορεί να περαστεί σε έναν καταχωρητή γενικού σκοπού 64 bit, όπως ο rax ή ο rsi, ή ως άμεση τιμή, όπως το 10 ή το 4. Μπορείς να θεωρήσεις ότι αυτό το πλήθος δεν είναι ποτέ αρνητικό.

Αυτή η μακροεντολή καλείται τρεις φορές, σε δύο διαφορετικές συναρτήσεις.

length = 8
page   = {32, 32, 32, 32, 32, 32, 32, 32}

width  = 8
page   = {61, 61, 61, 61, 32, 32, 32, 32} // '====' then spaces

3. Γράψε τη μακροεντολή lay_columns

Το πλέγμα είναι σταθερό για τη μορφή, οπότε οι αρχές των στηλών μπορούν να υπολογιστούν στον χρόνο συναρμολόγησης.

Όρισε τη μακροεντολή lay_columns, που γράφει COLUMN_COUNT μετατοπίσεις, καθεμία έναν ακέραιο 64 bit. Δέχεται δύο ορίσματα με αυτή τη σειρά:

  1. τη διεύθυνση προορισμού, που περνιέται σε έναν καταχωρητή γενικού σκοπού 64 bit
  2. το αριστερό περιθώριο, που περνιέται σε έναν καταχωρητή γενικού σκοπού 64 bit
lay_columns rsi, rax

Το πλήθος των μετατοπίσεων που θα γραφτούν στη διεύθυνση προορισμού είναι σταθερό και ίσο με COLUMN_COUNT.

Κάθε μετατόπιση είναι μια τιμή 64 bit που υπολογίζεται με offset[i] = margin + i * COLUMN_STRIDE, όπου i είναι η θέση της μετατόπισης ξεκινώντας από το 0, και margin είναι το δεύτερο όρισμα.

Αυτή η μακροεντολή μπορεί να χαλάσει τα δύο ορίσματα καταχωρητών της, αλλά κανένα άλλο. Καλείται μία φορά.

margin = 3
out    = {3, 21, 39, 57}

4. Γράψε τη μακροεντολή set_header

Καθεμία από τις τρεις μόνιμες ενότητες έχει στυλ επικεφαλίδας καθορισμένο από τη μορφή, οπότε το byte είναι γνωστό στον χρόνο συναρμολόγησης.

Όρισε τη set_header, που δέχεται δύο ορίσματα με αυτή τη σειρά:

  1. τη διεύθυνση προορισμού.
  2. ένα αναγνωριστικό στυλ, κυριολεκτικά, που περνιέται ως άμεση τιμή, όπου 0 σημαίνει απλό, 1 σημαίνει με γραμμή, και 2 σημαίνει με πλαίσιο.

Η διεύθυνση προορισμού είναι η έκφραση μιας ενεργού διεύθυνσης μνήμης. Δέχεται έναν βασικό καταχωρητή που μπορεί να αθροιστεί με έναν καταχωρητή θέσης κλιμακωμένο με 1, 2, 4 ή 8, και μια άμεση τιμή:

set_header rdx+8*rcx+2, 1

Διάλεξε το byte της επικεφαλίδας (PLAIN, RULED, ή BOXED) με συναρμολόγηση υπό συνθήκη (%if / %elif / %else) αντί για διακλάδωση κατά τον χρόνο εκτέλεσης.

Αυτή η μακροεντολή δεν πρέπει να χαλάσει κανέναν καταχωρητή. Καλείται τρεις φορές στην ίδια συνάρτηση.

pitch = 4
out   = {46, 0, 0, 0, 45, 0, 0, 0, 35} // '.', '-', '#'
Επεξεργασία μέσω GitHub Ο σύνδεσμος ανοίγει σε νέο παράθυρο ή καρτέλα
x86-64 Assembly Exercism

Έτοιμος να ξεκινήσεις την άσκηση Διάταξη περιοδικού;

Γράψου στο Exercism για να μάθεις και να κατακτήσεις x86-64 Assembly με 22 έννοιες130 ασκήσεις και πραγματική καθοδήγηση από ανθρώπους, όλα δωρεάν.