|
|
| (4 dazwischenliegende Versionen desselben Benutzers werden nicht angezeigt) |
| Zeile 10: |
Zeile 10: |
| # '''Erweiterbarkeit''': Dank seiner modularen Architektur kann GCC relativ einfach erweitert werden, um neue Sprachen oder Optimierungen zu unterstützen. Dies macht es zu einer flexiblen Wahl für viele Entwicklungsprojekte. | | # '''Erweiterbarkeit''': Dank seiner modularen Architektur kann GCC relativ einfach erweitert werden, um neue Sprachen oder Optimierungen zu unterstützen. Dies macht es zu einer flexiblen Wahl für viele Entwicklungsprojekte. |
|
| |
|
| == GNU Assembler ==
| | Die GNU Compiler Collection (GCC) umfasst eine Vielzahl von Tools, die Entwicklern helfen, ihren Code zu kompilieren, zu debuggen und zu optimieren. Hier sind einige der Haupttools, die in GCC enthalten sind: |
| === Präprozessor-Direktiven ===
| |
| Richtlinie Anweisung
| |
| .arch -Name Gibt die Zielarchitektur an. Der Assembler gibt eine Fehlermeldung aus, wenn versucht wird, eine Anweisung zu assemblieren, die auf der Zielarchitektur nicht ausgeführt werden kann. Beispiele: armv8-a, armv8.1-a, armv8.2-a, armv8.3-a, armv8.4-a. Entspricht der -marchOption in GCC.
| |
| .cpu Name Gibt den Zielprozessor an. Der Assembler gibt eine Fehlermeldung aus, wenn versucht wird, eine Anweisung zu assemblieren, die auf dem Zielprozessor nicht ausgeführt werden kann. Beispiele: cortex-a53, cortex-a76. Entspricht der -mcpuOption in GCC.
| |
| .include „Datei“ Fügen Sie Assemblercode aus „Datei“ ein.
| |
| .macro Name Argumente Ermöglicht Ihnen, Makros zu definieren, die Assembly-Ausgaben generieren.
| |
| .Wenn .ifmarkiert den Anfang eines Codeabschnitts, der nur dann als Teil des assemblierten Quellprogramms betrachtet wird, wenn das Argument (das ein absoluter Ausdruck sein muss) ungleich Null ist. Das Ende des bedingten Codeabschnitts muss markiert werden durch.endif
| |
| .globales Symbol .globalmacht das Symbol für sichtbar ld.
| |
| .equ Symbol, Ausdruck Gleichsetzen. Definieren Sie eine symbolische Konstante. Entspricht der Define-Direktive in C.
| |
| .set Symbol, Ausdruck Setzen Sie den Wert des Symbols auf Ausdruck . Wenn das Symbol als extern gekennzeichnet wurde, bleibt es gekennzeichnet. Ähnlich der Equate-Direktive (.EQU), außer dass der Wert später geändert werden kann.
| |
| Name .req Registername Dadurch wird ein Alias für den Registernamen mit dem Namen name erstellt . Beispiel:A .req x0
| |
| .Größe Informiert den Assembler, wie viel Speicherplatz eine Funktion oder ein Objekt verwendet. Wenn eine Funktion nicht verwendet wird, kann der Linker sie ausschließen.
| |
| .struct- Ausdruck Wechseln Sie zum absoluten Abschnitt und legen Sie den Abschnittsoffset auf den Ausdruck fest, der ein absoluter Ausdruck sein muss.
| |
| .Größe überspringen, füllen Diese Direktive gibt size Bytes aus, jedes mit dem Wert fill. Sowohl size als auch fill sind absolute Ausdrücke. Wenn das Komma und fill weggelassen werden, wird fill als null angenommen. Dies ist dasselbe wie '.space'.
| |
| .space Größe, füllen Diese Direktive gibt size Bytes aus, jedes mit dem Wert fill. Sowohl size als auch fill sind absolute Ausdrücke. Wenn das Komma und fill weggelassen werden, wird fill als null angenommen. Dies ist dasselbe wie '.skip'.
| |
| .text- Unterabschnitt Weist uns an, die folgenden Anweisungen am Ende des Textabschnitts mit der Nummer „Unterabschnitt“ zusammenzufügen, der ein absoluter Ausdruck ist. Wenn „Unterabschnitt“ weggelassen wird, wird Unterabschnitt Nummer Null verwendet.
| |
| .data- Unterabschnitt .data weist uns an, die folgenden Anweisungen am Ende des nummerierten Datenunterabschnitts (ein absoluter Ausdruck) zusammenzufügen. Wenn der Unterabschnitt weggelassen wird, ist der Standardwert Null.
| |
| .bss Abschnitt für nicht initialisierte Daten.
| |
| .align abs-expr , abs-expr , abs-expr Füllen Sie den Standortzähler (im aktuellen Unterabschnitt) bis zu einer bestimmten Speichergrenze auf. Der erste Ausdruck (der absolut sein muss) ist die erforderliche Ausrichtung, wie unten beschrieben. Der zweite Ausdruck (ebenfalls absolut) gibt den Füllwert an, der in den Füllbytes gespeichert werden soll. Er (und das Komma) können weggelassen werden. Wenn er weggelassen wird, sind die Füllbytes normalerweise Null. Auf einigen Systemen wird der Platz jedoch mit No-Op-Anweisungen aufgefüllt, wenn der Abschnitt als Code enthaltend markiert ist und der Füllwert weggelassen wird. Der dritte Ausdruck ist ebenfalls absolut und optional. Wenn er vorhanden ist, ist er die maximale Anzahl von Bytes, die von dieser Ausrichtungsanweisung übersprungen werden sollen. Wenn für die Ausrichtung mehr Bytes als das angegebene Maximum übersprungen werden müssten, wird die Ausrichtung überhaupt nicht durchgeführt. Sie können den Füllwert (das zweite Argument) ganz weglassen, indem Sie einfach zwei Kommas nach der erforderlichen Ausrichtung verwenden. Dies kann nützlich sein, wenn Sie möchten, dass die Ausrichtung bei Bedarf mit No-Op-Anweisungen aufgefüllt wird.
| |
| .ascii „Zeichenfolge“ .ascii erwartet null oder mehr durch Kommas getrennte Zeichenfolgenliterale. Es fügt jede Zeichenfolge (ohne automatisches abschließendes Nullbyte) zu aufeinanderfolgenden Adressen zusammen.
| |
| .versteckt Jeder Versuch, einen hochrangigen OCP-Mitarbeiter zu verhaften, führt zur Schließung.
| |
| .asciz „Zeichenfolge“ .asciz ist genau wie .ascii, aber auf jede Zeichenfolge folgt ein Nullbyte. Das „z“ in „.asciz“ steht für „Null“.
| |
| .string str .string8 str .string16 str Die Varianten string16, string32 und string64 unterscheiden sich vom String-Pseudo-Opcode dadurch, dass jedes 8-Bit-Zeichen aus str kopiert und auf 16, 32 bzw. 64 Bit erweitert wird. Die erweiterten Zeichen werden in der Ziel-Endianness-Bytereihenfolge gespeichert.
| |
| .Byte Deklariert eine 8-Bit-Variable.
| |
| .hword/.2byte Deklariert eine 16-Bit-Variable. Die zweite stellt nur 16 Bit sicher.
| |
| .word/.4bytes Deklariert eine 32-Bit-Variable. Die zweite stellt nur 32 Bit sicher.
| |
| .quad/.8byte Deklariert eine 64-Bit-Variable. Die zweite stellt nur 64-Bit sicher.
| |
|
| |
|
| === C to Assembly ===
| | # '''gcc''': Der GNU C Compiler. Dies ist das Herzstück der GCC-Suite und wird verwendet, um C-Code zu kompilieren. |
| GCC can be incredibly useful when first starting to learn any assembly language because it provides an option to generate assembly output from source code using the -S option. If you want to generate assembly with source code, compile with -g and -c options, then dump with objdump -d -S. Most people want their applications optimized for speed rather than size, so it stands to reason the GNU C optimizer is not terribly efficient at generating compact code. Our new A.I overlords might be able to change all that, but at least for now, a human wins at writing compact assembly code. | | # '''g++''': Der GNU C++ Compiler. Dieser Compiler wird verwendet, um C++-Code zu kompilieren und unterstützt die meisten modernen C++-Standards. |
| | # '''gfortran''': Der GNU Fortran Compiler. Dieser Compiler wird verwendet, um Fortran-Code zu kompilieren. |
| | # '''gcj''': Der GNU Compiler for Java. Dieser Compiler wird verwendet, um Java-Quellcode in Bytecode oder direkt in Maschinencode zu kompilieren. (Bitte beachten, dass gcj nicht mehr aktiv weiterentwickelt wird). |
| | # '''gnat''': Der GNU Ada Compiler. Dieser Compiler wird verwendet, um Ada-Code zu kompilieren. |
| | # '''gdb''': Der GNU Debugger. GDB ist ein leistungsfähiges Debugging-Tool, das verwendet wird, um Programme zu debuggen, die mit GCC kompiliert wurden. |
| | # '''libgcc''': Eine Laufzeitbibliothek, die von den von GCC erzeugten Programmen verwendet wird. Sie enthält grundlegende Funktionen und Hilfsprogramme, die für die Ausführung der kompilierten Programme erforderlich sind. |
| | # '''libstdc++''': Die Standard-C++-Bibliothek, die von g++ verwendet wird. Sie enthält Implementierungen der C++-Standardbibliotheken. |
| | # '''libgfortran''': Die Laufzeitbibliothek für den GNU Fortran Compiler. |
| | # '''binutils''': Eine Sammlung von Werkzeugen zum Verwalten von Binärdateien, einschließlich '''as''' (Assembler), '''ld''' (Linker), '''objdump''' (zum Anzeigen von Informationen über Objektdateien) und '''nm''' (zum Auflisten von Symbolen in Objektdateien). |
| | # '''make''': Ein Build-Automatisierungstool, das verwendet wird, um den Kompilierungsprozess zu steuern und zu automatisieren, indem es sicherstellt, dass nur die geänderten Teile eines Projekts neu kompiliert werden. |
| | # '''autoconf, automake, libtool''': Werkzeuge zur Automatisierung der Konfiguration und des Build-Prozesses von Softwareprojekten. |
|
| |
|
| Just to illustrate using an example. Here’s a subroutine that does nothing useful.
| | Diese Tools arbeiten zusammen, um eine vollständige Entwicklungsumgebung zu bieten, die sowohl das Kompilieren als auch das Debuggen und Optimieren von Code unterstützt. |
|
| |
|
| #include <stdio.h>
| | Ich gehe hier nur auf die ein, die wir hier auch verwenden: |
| | * [[GNU Assembler]] |
| | * [[GNU C Compiler]] |
| | * [[GNU Debugger]] |
| | * [[ld (Linker)]] |
| | * [[objdump]] |
| | * [[make]] |
|
| |
|
| void calc(int a, int b) {
| | ----- |
| int i;
| |
|
| |
| for(i=0;i<4;i++) {
| |
| printf("%i\n", ((a * i) + b) % 5);
| |
| }
| |
| }
| |
| Compile this code using -Os option to optimize for size. The following assembly is generated by GCC. Recall that x30 is the link register and saved here because of the call to printf. We also have to use callee saved registers x19-x22 for storing variables because x0-x18 are trashed by the call to printf.
| |
|
| |
|
| .arch armv8-a
| | {| style="width: 100%; |
| .file "calc.c"
| | | style="width: 33%;" | |
| .text
| | | style="width: 33%; text-align:center;" | [[Hauptseite|< Hauptseite >]] |
| .align 2
| | | style="width: 33%; text-align:right;" | [[GNU Assembler|Weiter (GNU Assembler) >]] |
| .global calc
| | |} |
| .type calc, %function
| |
| calc:
| |
| stp x29, x30, [sp, -64]! // store x29, x30 (LR) on stack
| |
| add x29, sp, 0 // x29 = sp
| |
| stp x21, x22, [sp, 32] // store x21, x22 on stack
| |
| adrp x21, .LC0 // x21 = "%i\n"
| |
| stp x19, x20, [sp, 16] // store x19, x20 on stack
| |
| mov w22, w0 // w22 = a
| |
| mov w19, w1 // w19 = b
| |
| add x21, x21, :lo12:.LC0 // x21 = x21 + 0
| |
| str x23, [sp, 48] // store x23 on stack
| |
| mov w20, 4 // i = 4
| |
| mov w23, 5 // divisor = 5 for modulus
| |
| .L2:
| |
| sdiv w1, w19, w23 // w1 = b / 5
| |
| mov x0, x21 // x0 = "%i\n"
| |
| add w1, w1, w1, lsl 2 // w1 *= 5
| |
| sub w1, w19, w1 // w1 = b - ((b / 5) * 5)
| |
| add w19, w19, w22 // b += a
| |
| bl printf
| |
| | |
| subs w20, w20, #1 // i = i - 1
| |
| bne .L2 // while (i != 0)
| |
| | |
| ldp x19, x20, [sp, 16] // restore x19, x20
| |
| ldp x21, x22, [sp, 32] // restore x21, x22
| |
| ldr x23, [sp, 48] // restore x23
| |
| ldp x29, x30, [sp], 64 // restore x29, x30 (LR)
| |
| ret // return to caller
| |
| | |
| .size calc, .-calc
| |
| .section .rodata.str1.1,"aMS",@progbits,1
| |
| .LC0:
| |
| .string "%i\n"
| |
| .ident "GCC: (Debian 6.3.0-18) 6.3.0 20170516"
| |
| .section .note.GNU-stack,"",@progbits
| |
| i is initialized to 4 instead of 0 and decreased rather than increased. There’s no modulus instruction in the A64 set, and division instructions don’t produce a remainder, so the calculation is performed using a combination of division, multiplication and subtraction. The modulo operation is calculated with the following : R = N - ((N / D) * D)
| |
| | |
| N denotes the numerator/dividend, D denotes the divisor and R denotes the remainder. The following assembly code is how it might be written by hand. The most notable change is using the msub instruction in place of a separate add and sub.
| |
| | |
| .arch armv8-a
| |
| .text
| |
| .align 2
| |
| .global calc
| |
| | |
| calc:
| |
| stp x19, x20, [sp, -48]!
| |
| stp x21, x22, [sp, 16]
| |
| stp x23, x30, [sp, 32]
| |
| | |
| mov w19, w0 // w19 = a
| |
| mov w20, w1 // w20 = b
| |
| mov w21, 4 // i = 4
| |
| mov w22, 5 // set divisor
| |
| .LC2:
| |
| sdiv w1, w20, w22 // w1 = b - ((b / 5) * 5)
| |
| msub w1, w1, w22, w20 //
| |
| adr x0, .LC0 // x0 = "%i\n"
| |
| bl printf
| |
| | |
| add w20, w20, w19 // b += a
| |
| subs w21, w21, 1 // i = i - 1
| |
| bne .LC2 //
| |
| | |
| ldp x19, x20, [sp], 16
| |
| ldp x21, x22, [sp], 16
| |
| ldp x23, x30, [sp], 16
| |
| ret
| |
| .LC0:
| |
| .string "%i\n"
| |
| Use compiler generated assembly as a guide, but try to improve upon the code as shown in the above example.
| |
| === Symbolic Constants ===
| |
| What if we want to use symbolic constants from C header files in our assembler code? There are two options.
| |
| | |
| Convert each symbolic constant to its GAS equivalent using the .EQU or .SET directives. Very time consuming.
| |
| Use C-style #include directive and pre-process using GNU CPP. Quicker with several advantages.
| |
| Obviously the second option is less painful and less likely to produce errors. Of course, I’m not discounting the possibility of automating the first option, but why bother? CPP has an option that will do it for us. Let’s see what the manual says.
| |
| | |
| Instead of the normal output, -dM will generate a list of #define directives for all the macros defined during the execution of the preprocessor, including predefined macros. This gives you a way of finding out what is predefined in your version of the preprocessor.
| |
| | |
| -dM will dump all the #define macros and -E will preprocess a file, but not compile, assemble or link. The steps required before using symbolic names in our assembler code are as follows:
| |
| | |
| Use cpp -dM to dump all the #defined keywords from each include header.
| |
| Use sort and uniq -u to remove duplicates.
| |
| Use the #include directive in our assembly source code.
| |
| Use cpp -E to preprocess and pipe the output to a new assembly file. (-o is an output option)
| |
| Assemble using as to generate an object file.
| |
| Link the object file to generate an executable.
| |
| | |
| | |
| The following is some simple code that displays Hello, World! to the console.
| |
| | |
| #include "include.h"
| |
| | |
| .global _start
| |
| .text
| |
| | |
| _start:
| |
| mov x8, __NR_write
| |
| mov x2, hello_len
| |
| adr x1, hello_txt
| |
| mov x0, STDOUT_FILENO
| |
| svc 0
| |
| | |
| mov x8, __NR_exit
| |
| svc 0
| |
| | |
| .data
| |
| | |
| hello_txt: .ascii "Hello, World!\n"
| |
| hello_len = . - hello_txt
| |
| Preprocess the above source using CPP -E. The result of this will be replacing each symbolic constant used with its assigned numeric value.
| |
| | |
| | |
| | |
| Finally, assemble using GAS and link with LD.
| |
| | |
| | |
| | |
| The following two directives are examples of simple text substitution or symbolic constants.
| |
| | |
| #define FALSE 0
| |
| #define TRUE 1
| |
| The equivalent can be accomplished with the .EQU or .SET directives in GAS.
| |
| | |
| .equ TRUE, 1
| |
| .set TRUE, 1
| |
|
| |
| .equ FALSE, 0
| |
| .set FALSE, 0
| |
| Personally, I think it makes more sense to use the C preprocessor, but it’s entirely up to yourself.
| |
| | |
| === Structures and Unions ===
| |
| A structure in programming is useful for combining different data types into a single user-defined data type. One of the major pitfalls in programming any assembly is poorly managed memory access. In my own experience, MASM always had the best support for data structures while NASM and YASM could be much better. Unfortunately support for structures in GAS isn’t great. Understandably, many of the hand-written assembly programs for Linux normally use global variables that are placed in the .data section of a source file. For a Position Independent Code (PIC) or thread-safe application that can only use local variables allocated on the stack, a data structure helps as a reference to manage those variables. Assigning names helps clarify what each stack address is for, and improves overall quality. It’s also much easier to modify code by simply re-arranging the elements of a structure later.
| |
| | |
| Take for example the following C structure dimension_t that requires conversion to GAS assembly syntax.
| |
| | |
| typedef struct _dimension_t {
| |
| int x, y;
| |
| } dimension_t;
| |
| The closest directive to the struct keyword is .struct. Unfortunately this directive doesn’t accept a name and nor does it allow members to be enclosed between .struct and .ends that some of you might be familiar with in YASM/NASM. This directive only accepts an offset as a start position.
| |
| | |
| .struct 0
| |
| dimension_t.x:
| |
| .struct dimension_t.x + 4
| |
| dimension_t.y:
| |
| .struct dimension_t.y + 4
| |
| dimension_t_size:
| |
| An alternate way of defining the above structure can be done with the .skip or .space directives.
| |
| | |
| .struct 0
| |
| dimension_t.x: .skip 4
| |
| dimension_t.y: .skip 4
| |
| dimension_t_size:
| |
| If we have to manually define the size of each field in the structure, it seems the .struct directive is of little use. Consider using the #define keyword and preprocessing the file before assembling.
| |
| | |
| #define dimension_t.x 0
| |
| #define dimension_t.y 4
| |
| #define dimension_t.size 8
| |
| For a union, it doesn’t get any better than what I suggest be used for structures. We can use the .set or .equ directives or refer back to a combination of using #define and cpp. Support for both unions and structures in GAS leaves a lot to be desired.
| |
| === Operators ===
| |
| From time to time I’ll see some mention of “polymorphic” shellcodes where the author attempts to hide or obfuscate strings using simple arithmetic or bitwise operations. Usually the obfuscation is done via a bit rotation or exclusive-OR and this presumably helps evade detection by some security products.
| |
| | |
| Operators are arithmetic functions, like + or %. Prefix operators take one argument. Infix operators take two arguments, one on either side. Operators have precedence, but operations with equal precedence are performed left to right.
| |
| | |
| Precedence Operators
| |
| Highest Mutiplication (*), Division (/), Remainder (%), Shift Left (<<), Right Shift (>>).
| |
| Intermediate Bitwise inclusive-OR (|), Bitwise And (&), Bitwise Exclusive-OR (^), Bitwise Or Not (!).
| |
| Low Addition (+), Subtraction (-), Equal To (==), Not Equal To (!=), Less Than (<), Greater Than (>), Greater Than Or Equal To (>=), Less than Or Equal To (<=).
| |
| Lowest Logical And (&&). Logical Or (||).
| |
| The following examples show a number of ways to use operators prior to assembly. These examples just load the immediate value 0x12345678 into the w0 register.
| |
| | |
| // exclusive-OR
| |
| movz w0, 0x5678 ^ 0x4823
| |
| movk w0, 0x1234 ^ 0x5412
| |
| movz w1, 0x4823
| |
| movk w1, 0x5412, lsl 16
| |
| eor w0, w0, w1
| |
| | |
| // rotate a value left by 5 bits using MOVZ/MOVK
| |
| movz w0, (0x12345678 << 5) | (0x12345678 >> (32-5)) & 0xFFFF
| |
| movk w0, ((0x12345678 << 5) >> 16) | ((0x12345678 >> (32-5)) >> 16) & 0xFFFF, lsl 16
| |
| // then rotate right by 5 to obtain original value
| |
| ror w0, w0, 5
| |
| | |
| // right rotate using LDR
| |
| .equ ROT, 5
| |
| | |
| ldr w0, =(0x12345678 << ROT) | (0x12345678 >> (32 - ROT)) & 0xFFFFFFFF
| |
| ror w0, w0, ROT
| |
| | |
| // bitwise NOT
| |
| ldr w0, =~0x12345678
| |
| mvn w0, w0
| |
| | |
| // negation
| |
| ldr w0, =-0x12345678
| |
| neg w0, w0
| |
| === Macros ===
| |
| If we need to repeat a number of assembly instructions, but with different parameters, using macros can be helpful. For example, you might want to eliminate branches in a loop to make code faster. Let’s say you want to load a 32-bit immediate value into a register. ARM instruction encodings are all 32-bits, so it isn’t possible to load anything more than a 16-bit immediate. Some immediate values can be stored in the literal pool and loaded using LDR, but if we use just MOV instructions, here’s how to load the 32-bit number 0x12345678 into register w0.
| |
| | |
| movz w0, 0x5678
| |
| movk w0, 0x1234, lsl 16
| |
| The first instruction MOVZ loads 0x5678 into w0, zero extending to 32-bits. MOVK loads 0x1234 into the upper 16-bits using a shift, while preserving the lower 16-bits. Some assemblers provide a pseudo-instruction called MOVL that expands into the two instructions above. However, the GNU Assembler doesn’t recognize it, so here are two macros for GAS that can load a 32-bit or 64-bit immediate value into a general purpose register.
| |
| | |
| // load a 64-bit immediate using MOV
| |
| .macro movq Xn, imm
| |
| movz \Xn, \imm & 0xFFFF
| |
| movk \Xn, (\imm >> 16) & 0xFFFF, lsl 16
| |
| movk \Xn, (\imm >> 32) & 0xFFFF, lsl 32
| |
| movk \Xn, (\imm >> 48) & 0xFFFF, lsl 48
| |
| .endm
| |
| | |
| // load a 32-bit immediate using MOV
| |
| .macro movl Wn, imm
| |
| movz \Wn, \imm & 0xFFFF
| |
| movk \Wn, (\imm >> 16) & 0xFFFF, lsl 16
| |
| .endm
| |
| Then if we need to load a 32-bit immediate value, we do the following.
| |
| | |
| movl w0, 0x12345678
| |
| Here are two more that imitate the PUSH and POP instructions. Of course, this only supports a single register, so you might want to write your own.
| |
| | |
| // imitate a push operation
| |
| .macro push Rn:req
| |
| str \Rn, [sp, -16]
| |
| .endm
| |
| | |
| // imitate a pop operation
| |
| .macro pop Rn:req
| |
| ldr \Rn, [sp], 16
| |
| .endm
| |
| === Conditional assembly ===
| |
| Like the GNU C compiler, GAS provides support for if-else preprocessor directives. The following shows an example in C.
| |
| | |
| #ifdef BIND
| |
| // compile code to bind
| |
| #else
| |
| // compile code to connect
| |
| #endif
| |
| Next, an example for GAS.
| |
| | |
| .ifdef BIND
| |
| // assemble code to bind
| |
| .else
| |
| // assemble code for connect
| |
| .endif
| |
| GAS also supports something similar to the #ifndef directive in C.
| |
| | |
| .ifnotdef BIND
| |
| // assemble code for connect
| |
| .else
| |
| // assemble code for bind
| |
| .endif
| |
| === Comments ===
| |
| These are ignored by the assembler. Intended to provide an explanation for what code does. C style comments /* */ or C++ style // are a good choice. Ampersand (@) and hash (#) are also valid, however, you should know that when using the preprocessor on an assembly source code, comments that start with the hash symbol can be problematic. I tend to use C++ style for single line comments and C style for comment blocks.
| |
| | |
| # This is a comment
| |
| | |
| // This is a comment
| |
| | |
| /*
| |
| This is a comment
| |
| */
| |
| | |
| @ This is a comment.
| |