|
|
| (19 dazwischenliegende Versionen desselben Benutzers werden nicht angezeigt) |
| Zeile 1: |
Zeile 1: |
| == GNU Assembler ==
| | Der GNU Assembler, häufig als GAS bezeichnet, ist ein Teil der GNU Binutils und ein wesentlicher Bestandteil der GNU Compiler Collection (GCC). Hier sind einige wichtige Punkte über den GNU Assembler: |
| | |
| | #Zweck und Funktion: GAS ist ein Assembler, der Quellcode in Assemblersprache in Maschinencode übersetzt. Dies ist ein wichtiger Schritt im Kompilierungsprozess, insbesondere bei der Umwandlung von Hochsprachen wie C oder C++ in ausführbare Programme. |
| | #Unterstützte Architekturen: GAS unterstützt eine Vielzahl von Prozessorarchitekturen, darunter x86, ARM, MIPS, PowerPC und viele mehr. Diese Vielseitigkeit macht es zu einem äußerst nützlichen Werkzeug für die Entwicklung auf verschiedenen Plattformen. |
| | #Syntax: GAS verwendet eine spezielle Syntax, die als AT&T-Syntax bekannt ist. Diese unterscheidet sich von der Intel-Syntax, die in vielen anderen Assemblern verwendet wird. Zum Beispiel verwendet GAS das Format opcode source, destination, während die Intel-Syntax opcode destination, source verwendet. |
| | #Integration mit GCC: Da GAS ein Teil der GNU Binutils ist, arbeitet es nahtlos mit GCC zusammen. Wenn Sie ein Programm mit GCC kompilieren, wird der Quellcode in der Regel zuerst in Assemblercode und dann in Maschinencode umgewandelt, wobei GAS diese Umwandlung übernimmt. |
| | #Befehlszeilenoptionen: GAS bietet eine Vielzahl von Befehlszeilenoptionen, die es Entwicklern ermöglichen, die Assemblierung fein abzustimmen. Einige gängige Optionen sind -o zum Festlegen des Ausgabedateinamens und -g zum Hinzufügen von Debugging-Informationen. |
| | #Lizenz: GAS wird unter der GNU General Public License (GPL) veröffentlicht. Dies bedeutet, dass es frei verfügbar ist und jeder den Quellcode einsehen, modifizieren und weiterverbreiten kann, solange die Bedingungen der GPL eingehalten werden. |
| | |
| | Im folgenden beschreibe ich eine Auswahl von Präprozessor-Direktiven, die Präfixoperatoren und Infix-Operatoren. Zusätzlich gehe ich auf Bedingte Assemblierung und Makros ein. |
| | |
| | Zusätzliche Themen zum GNU Assembler: |
| | *[[C nach Assembler]] |
| | *[[Schleifen in Assembler]] |
| | *[[Strukturen in Assembler]] |
| | |
| == Präprozessor-Direktiven == | | == Präprozessor-Direktiven == |
|
| |
|
| Zeile 60: |
Zeile 75: |
| | '''.unreq alias-name''' || Dadurch wird ein Registeralias gelöscht, der zuvor mit der .req Direktive definiert wurde. | | | '''.unreq alias-name''' || Dadurch wird ein Registeralias gelöscht, der zuvor mit der .req Direktive definiert wurde. |
| |} | | |} |
|
| |
| == Bedingte Assemblierung ==
| |
| :
| |
| .if .else .endif
| |
|
| |
| == Makros ==
| |
| .macro
| |
| The commands .macro and .endm allow you to define macros that generate assembly output. For example, this definition specifies a macro sum that puts a sequence of numbers into memory:
| |
|
| |
| .macro sum from=0, to=5
| |
| .long \from
| |
| .if \to-\from
| |
| sum "(\from+1)",\to
| |
| .endif
| |
| .endm
| |
| With that definition, ‘SUM 0,5’ is equivalent to this assembly input:
| |
|
| |
| .long 0
| |
| .long 1
| |
| .long 2
| |
| .long 3
| |
| .long 4
| |
| .long 5
| |
| .macro macname
| |
| .macro macname macargs …
| |
| Begin the definition of a macro called macname. If your macro definition requires arguments, specify their names after the macro name, separated by commas or spaces. You can qualify the macro argument to indicate whether all invocations must specify a non-blank value (through ‘:req’), or whether it takes all of the remaining arguments (through ‘:vararg’). You can supply a default value for any macro argument by following the name with ‘=deflt’. You cannot define two macros with the same macname unless it has been subject to the .purgem directive (see .purgem name) between the two definitions. For example, these are all valid .macro statements:
| |
|
| |
| .macro comm
| |
| Begin the definition of a macro called comm, which takes no arguments.
| |
|
| |
| .macro plus1 p, p1
| |
| .macro plus1 p p1
| |
| Either statement begins the definition of a macro called plus1, which takes two arguments; within the macro definition, write ‘\p’ or ‘\p1’ to evaluate the arguments.
| |
|
| |
| .macro reserve_str p1=0 p2
| |
| Begin the definition of a macro called reserve_str, with two arguments. The first argument has a default value, but not the second. After the definition is complete, you can call the macro either as ‘reserve_str a,b’ (with ‘\p1’ evaluating to a and ‘\p2’ evaluating to b), or as ‘reserve_str ,b’ (with ‘\p1’ evaluating as the default, in this case ‘0’, and ‘\p2’ evaluating to b).
| |
|
| |
| .macro m p1:req, p2=0, p3:vararg
| |
| Begin the definition of a macro called m, with at least three arguments. The first argument must always have a value specified, but not the second, which instead has a default value. The third formal will get assigned all remaining arguments specified at invocation time.
| |
|
| |
| When you call a macro, you can specify the argument values either by position, or by keyword. For example, ‘sum 9,17’ is equivalent to ‘sum to=17, from=9’. You can also omit values when using keywords, so for example ‘sum to=6’ is equivalent to ‘sum 0, 6’.
| |
|
| |
| Note however that when operating in altmacro mode arguments can only be specified by position, not keyword. See .altmacro.
| |
|
| |
| Thus for example:
| |
|
| |
| .macro foo bar=1, baz=2
| |
| .print "\bar \baz"
| |
| .endm
| |
|
| |
| foo baz=3
| |
| .altmacro
| |
| foo baz=3
| |
| Will print:
| |
|
| |
| 1 3
| |
| baz=3 2
| |
| Note that since each of the macargs can be an identifier exactly as any other one permitted by the target architecture, there may be occasional problems if the target hand-crafts special meanings to certain characters when they occur in a special position. For example, if the colon (:) is generally permitted to be part of a symbol name, but the architecture specific code special-cases it when occurring as the final character of a symbol (to denote a label), then the macro parameter replacement code will have no way of knowing that and consider the whole construct (including the colon) an identifier, and check only this identifier for being the subject to parameter substitution. So for example this macro definition:
| |
|
| |
| .macro label l
| |
| \l:
| |
| .endm
| |
| might not work as expected. Invoking ‘label foo’ might not create a label called ‘foo’ but instead just insert the text ‘\l:’ into the assembler source, probably generating an error about an unrecognised identifier.
| |
|
| |
| Similarly problems might occur with the period character (‘.’) which is often allowed inside opcode names (and hence identifier names). So for example constructing a macro to build an opcode from a base name and a length specifier like this:
| |
|
| |
| .macro opcode base length
| |
| \base.\length
| |
| .endm
| |
| and invoking it as ‘opcode store l’ will not create a ‘store.l’ instruction but instead generate some kind of error as the assembler tries to interpret the text ‘\base.\length’.
| |
|
| |
| There are several possible ways around this problem:
| |
|
| |
| Insert white space
| |
| If it is possible to use white space characters then this is the simplest solution. eg:
| |
|
| |
| .macro label l
| |
| \l :
| |
| .endm
| |
| Use ‘\()’
| |
| The string ‘\()’ can be used to separate the end of a macro argument from the following text. eg:
| |
|
| |
| .macro opcode base length
| |
| \base\().\length
| |
| .endm
| |
| Use the alternate macro syntax mode
| |
| In the alternative macro syntax mode the ampersand character (‘&’) can be used as a separator. eg:
| |
|
| |
| .altmacro
| |
| .macro label l
| |
| l&:
| |
| .endm
| |
| Note: this problem of correctly identifying string parameters to pseudo ops also applies to the identifiers used in .irp (see .irp symbol,value…) and .irpc (see .irpc symbol,values…) as well.
| |
|
| |
| Another issue can occur with the actual arguments passed during macro invocation: Multiple arguments can be separated by blanks or commas. To have arguments actually contain blanks or commas (or potentially other non-alpha- numeric characters), individual arguments will need to be enclosed in either parentheses (), square brackets [], or double quote " characters. The latter may be the only viable option in certain situations, as only double quotes are actually stripped while establishing arguments. It may be important to be aware of two escaping models used when processing such quoted argument strings: For one two adjacent double quotes represent a single double quote in the resulting argument, going along the lines of the stripping of the enclosing quotes. But then double quotes can also be escaped by a backslash \, but this backslash will not be retained in the resulting actual argument as then seen / used while expanding the macro.
| |
|
| |
| As a consequence to the first of these escaping mechanisms two string literals intended to be representing separate macro arguments need to be separated by white space (or, better yet, by a comma). To state it differently, such adjacent string literals - even if separated only by a blank - will not be concatenated when determining macro arguments, even if they’re only separated by white space. This is unlike certain other pseudo ops, e.g. .ascii.
| |
|
| |
| .endm
| |
| Mark the end of a macro definition.
| |
|
| |
| .exitm
| |
| Exit early from the current macro definition.
| |
|
| |
| \@
| |
| as maintains a counter of how many macros it has executed in this pseudo-variable; you can copy that number to your output with ‘\@’, but only within a macro definition.
| |
|
| |
| \+
| |
| Similar to the \@ pseudo-variable, as also maintains a per-macro count of the number of times that that macro has been executed. You can copy that number to your output with ‘\+’, but only within a macro definition.
| |
|
| |
| LOCAL name [ , … ]
| |
| Warning: LOCAL is only available if you select “alternate macro syntax” with ‘--alternate’ or .altmacro. See .altmacro.
| |
|
| |
|
| |
|
| |
|
| == Präfixoperator == | | == Präfixoperator == |
| Zeile 228: |
Zeile 129: |
| |} | | |} |
|
| |
|
| | == Bedingte Assemblierung == |
| | Bedingte Assemblierung im GNU Assembler (GAS) ist eine mächtige Technik, die es ermöglicht, Teile des Assemblercodes basierend auf bestimmten Bedingungen einzuschließen oder auszuschließen. Dies ist besonders nützlich, wenn man für verschiedene Architekturen oder Konfigurationen, wie z.B. ARM64, entwickelt. Hier sind die wichtigsten Aspekte der bedingten Assemblierung im Zusammenhang mit ARM64-Assembler: |
|
| |
|
| | === Grundlegende Direktiven für Bedingte Assemblierung === |
| | *'''.if / .else / .endif''': Diese Direktiven werden verwendet, um Codeblöcke basierend auf einer Bedingung ein- oder auszuschließen. |
| | *'''.ifdef / .ifndef''': Diese Direktiven überprüfen, ob ein Symbol definiert ist oder nicht. |
| | *'''.elif''': Diese Direktive ermöglicht alternative Codepfade, wenn die ursprüngliche Bedingung nicht erfüllt ist. |
| | *'''.define / .undef''': Diese Direktiven definieren Symbole oder heben deren Definition auf. |
| | === Beispiele für Bedingte Assemblierung im ARM64-Assembler === |
| | ==== Einfache Bedingte Assemblierung mit .if, .else und .endif ==== |
| | <syntaxhighlight lang="asm"> |
| | .equ DEBUG, 1 // Definiert ein Symbol DEBUG mit dem Wert 1 |
|
| |
|
| 9.4.4 ARM Machine Directives (-> https://sourceware.org/binutils/docs-2.25/as/ARM-Directives.html#ARM-Directives)
| | .if DEBUG |
| | | // Dieser Code wird assembliert, wenn DEBUG nicht null ist |
| | | .ascii "Debugging enabled\n" |
| .2byte expression [, expression]*
| | .else |
| .4byte expression [, expression]*
| | // Dieser Code wird assembliert, wenn DEBUG null ist |
| .8byte expression [, expression]*
| | .ascii "Debugging disabled\n" |
| These directives write 2, 4 or 8 byte values to the output section.
| | .endif |
| | | </syntaxhighlight> |
| | | ==== Bedingte Assemblierung mit .ifdef und .ifndef ==== |
| .align expression [, expression]
| | <syntaxhighlight lang="asm"> |
| This is the generic .align directive. For the ARM however if the first argument is zero (ie no alignment is needed) the assembler will behave as if the argument had been 2 (ie pad to the next four byte boundary). This is for compatibility with ARM's own assembler.
| | #define FEATURE_ENABLED // Definiert ein Symbol FEATURE_ENABLED |
| | |
| | |
| .arch name
| |
| Select the target architecture. Valid values for name are the same as for the -march commandline option.
| |
| Specifying .arch clears any previously selected architecture extensions.
| |
| | |
| | |
| | |
| .arch_extension name
| |
| Add or remove an architecture extension to the target architecture. Valid values for name are the same as those accepted as architectural extensions by the -mcpu commandline option.
| |
| .arch_extension may be used multiple times to add or remove extensions incrementally to the architecture being compiled for.
| |
| | |
| | |
| | |
| .arm
| |
| This performs the same action as .code 32.
| |
| | |
| | |
| .bss
| |
| This directive switches to the .bss section.
| |
| | |
| | |
| .cantunwind
| |
| Prevents unwinding through the current function. No personality routine or exception table data is required or permitted.
| |
| | |
| | |
| .code [16|32]
| |
| This directive selects the instruction set being generated. The value 16 selects Thumb, with the value 32 selecting ARM.
| |
| | |
| | |
| .cpu name
| |
| Select the target processor. Valid values for name are the same as for the -mcpu commandline option.
| |
| Specifying .cpu clears any previously selected architecture extensions.
| |
| | |
| | |
| name .dn register name [.type] [[index]]
| |
| name .qn register name [.type] [[index]]
| |
| The dn and qn directives are used to create typed and/or indexed register aliases for use in Advanced SIMD Extension (Neon) instructions. The former should be used to create aliases of double-precision registers, and the latter to create aliases of quad-precision registers.
| |
| If these directives are used to create typed aliases, those aliases can be used in Neon instructions instead of writing types after the mnemonic or after each operand. For example:
| |
| | |
| x .dn d2.f32
| |
| y .dn d3.f32
| |
| z .dn d4.f32[1]
| |
| vmul x,y,z
| |
|
| |
| This is equivalent to writing the following:
| |
| | |
| vmul.f32 d2,d3,d4[1]
| |
|
| |
| Aliases created using dn or qn can be destroyed using unreq.
| |
| | |
| | |
| .eabi_attribute tag, value
| |
| Set the EABI object attribute tag to value.
| |
| The tag is either an attribute number, or one of the following: Tag_CPU_raw_name, Tag_CPU_name, Tag_CPU_arch, Tag_CPU_arch_profile, Tag_ARM_ISA_use, Tag_THUMB_ISA_use, Tag_FP_arch, Tag_WMMX_arch, Tag_Advanced_SIMD_arch, Tag_PCS_config, Tag_ABI_PCS_R9_use, Tag_ABI_PCS_RW_data, Tag_ABI_PCS_RO_data, Tag_ABI_PCS_GOT_use, Tag_ABI_PCS_wchar_t, Tag_ABI_FP_rounding, Tag_ABI_FP_denormal, Tag_ABI_FP_exceptions, Tag_ABI_FP_user_exceptions, Tag_ABI_FP_number_model, Tag_ABI_align_needed, Tag_ABI_align_preserved, Tag_ABI_enum_size, Tag_ABI_HardFP_use, Tag_ABI_VFP_args, Tag_ABI_WMMX_args, Tag_ABI_optimization_goals, Tag_ABI_FP_optimization_goals, Tag_compatibility, Tag_CPU_unaligned_access, Tag_FP_HP_extension, Tag_ABI_FP_16bit_format, Tag_MPextension_use, Tag_DIV_use, Tag_nodefaults, Tag_also_compatible_with, Tag_conformance, Tag_T2EE_use, Tag_Virtualization_use
| |
| | |
| The value is either a number, "string", or number, "string" depending on the tag.
| |
| | |
| Note - the following legacy values are also accepted by tag: Tag_VFP_arch, Tag_ABI_align8_needed, Tag_ABI_align8_preserved, Tag_VFP_HP_extension,
| |
| | |
| | |
| | |
| .even | |
| This directive aligns to an even-numbered address.
| |
| | |
| | |
| .extend expression [, expression]*
| |
| .ldouble expression [, expression]* | |
| These directives write 12byte long double floating-point values to the output section. These are not compatible with current ARM processors or ABIs.
| |
| | |
| | |
| .fnend
| |
| Marks the end of a function with an unwind table entry. The unwind index table entry is created when this directive is processed.
| |
| If no personality routine has been specified then standard personality routine 0 or 1 will be used, depending on the number of unwind opcodes required.
| |
| | |
| | |
| | |
| .fnstart
| |
| Marks the start of a function with an unwind table entry.
| |
| | |
| | |
| .force_thumb
| |
| This directive forces the selection of Thumb instructions, even if the target processor does not support those instructions
| |
| | |
| | |
| .fpu name
| |
| Select the floating-point unit to assemble for. Valid values for name are the same as for the -mfpu commandline option.
| |
| | |
| | |
| .handlerdata
| |
| Marks the end of the current function, and the start of the exception table entry for that function. Anything between this directive and the .fnend directive will be added to the exception table entry.
| |
| Must be preceded by a .personality or .personalityindex directive.
| |
| | |
| | |
| .inst opcode [ , ... ]
| |
| .inst.n opcode [ , ... ]
| |
| .inst.w opcode [ , ... ]
| |
| Generates the instruction corresponding to the numerical value opcode. .inst.n and .inst.w allow the Thumb instruction size to be specified explicitly, overriding the normal encoding rules.
| |
| .ldouble expression [, expression]*
| |
| See .extend.
| |
| | |
| | |
| .ltorg
| |
| This directive causes the current contents of the literal pool to be dumped into the current section (which is assumed to be the .text section) at the current location (aligned to a word boundary). GAS maintains a separate literal pool for each section and each sub-section. The .ltorg directive will only affect the literal pool of the current section and sub-section. At the end of assembly all remaining, un-empty literal pools will automatically be dumped.
| |
| Note - older versions of GAS would dump the current literal pool any time a section change occurred. This is no longer done, since it prevents accurate control of the placement of literal pools.
| |
| | |
| | |
| .movsp reg [, #offset]
| |
| Tell the unwinder that reg contains an offset from the current stack pointer. If offset is not specified then it is assumed to be zero.
| |
| | |
| | |
| .object_arch name
| |
| Override the architecture recorded in the EABI object attribute section. Valid values for name are the same as for the .arch directive. Typically this is useful when code uses runtime detection of CPU features.
| |
| | |
| | |
| .packed expression [, expression]*
| |
| This directive writes 12-byte packed floating-point values to the output section. These are not compatible with current ARM processors or ABIs.
| |
| | |
| | |
| .pad #count
| |
| Generate unwinder annotations for a stack adjustment of count bytes. A positive value indicates the function prologue allocated stack space by decrementing the stack pointer.
| |
| | |
| | |
| .personality name
| |
| Sets the personality routine for the current function to name.
| |
| | |
| | |
| .personalityindex index
| |
| Sets the personality routine for the current function to the EABI standard routine number index
| |
| | |
| | |
| .pool
| |
| This is a synonym for .ltorg.
| |
| | |
| | |
| name .req register name
| |
| This creates an alias for register name called name. For example:
| |
| foo .req r0
| |
|
| |
| | |
| | |
| .save reglist
| |
| Generate unwinder annotations to restore the registers in reglist. The format of reglist is the same as the corresponding store-multiple instruction.
| |
|
| |
| core registers
| |
| | |
| .save {r4, r5, r6, lr}
| |
| stmfd sp!, {r4, r5, r6, lr}
| |
|
| |
| FPA registers
| |
| | |
| .save f4, 2
| |
| sfmfd f4, 2, [sp]!
| |
|
| |
| VFP registers
| |
| | |
| .save {d8, d9, d10}
| |
| fstmdx sp!, {d8, d9, d10}
| |
|
| |
| iWMMXt registers
| |
| | |
| .save {wr10, wr11}
| |
| wstrd wr11, [sp, #-8]!
| |
| wstrd wr10, [sp, #-8]!
| |
| or
| |
| .save wr11
| |
| wstrd wr11, [sp, #-8]!
| |
| .save wr10
| |
| wstrd wr10, [sp, #-8]!
| |
|
| |
| | |
| | |
| .setfp fpreg, spreg [, #offset]
| |
| Make all unwinder annotations relative to a frame pointer. Without this the unwinder will use offsets from the stack pointer.
| |
| The syntax of this directive is the same as the add or mov instruction used to set the frame pointer. spreg must be either sp or mentioned in a previous .movsp directive.
| |
| | |
| .movsp ip
| |
| mov ip, sp
| |
| ...
| |
| .setfp fp, ip, #4
| |
| add fp, ip, #4
| |
|
| |
| | |
| | |
| .secrel32 expression [, expression]*
| |
| This directive emits relocations that evaluate to the section-relative offset of each expression's symbol. This directive is only supported for PE targets.
| |
| | |
| | |
| .syntax [unified | divided]
| |
| This directive sets the Instruction Set Syntax as described in the ARM-Instruction-Set section.
| |
| | |
| | |
| .thumb
| |
| This performs the same action as .code 16.
| |
| | |
| | |
| .thumb_func
| |
| This directive specifies that the following symbol is the name of a Thumb encoded function. This information is necessary in order to allow the assembler and linker to generate correct code for interworking between Arm and Thumb instructions and should be used even if interworking is not going to be performed. The presence of this directive also implies .thumb
| |
| This directive is not neccessary when generating EABI objects. On these targets the encoding is implicit when generating Thumb code.
| |
| | |
| | |
| | |
| .thumb_set
| |
| This performs the equivalent of a .set directive in that it creates a symbol which is an alias for another symbol (possibly not yet defined). This directive also has the added property in that it marks the aliased symbol as being a thumb function entry point, in the same way that the .thumb_func directive does.
| |
|
| |
|
| | .ifdef FEATURE_ENABLED |
| | // Dieser Code wird assembliert, wenn FEATURE_ENABLED definiert ist |
| | .ascii "Feature is enabled\n" |
| | .else |
| | // Dieser Code wird assembliert, wenn FEATURE_ENABLED nicht definiert ist |
| | .ascii "Feature is disabled\n" |
| | .endif |
|
| |
|
| .tlsdescseq tls-variable
| | #undef FEATURE_ENABLED // Hebt die Definition von FEATURE_ENABLED auf |
| This directive is used to annotate parts of an inlined TLS descriptor trampoline. Normally the trampoline is provided by the linker, and this directive is not needed.
| |
|
| |
|
| | .ifndef FEATURE_ENABLED |
| | // Dieser Code wird assembliert, wenn FEATURE_ENABLED nicht definiert ist |
| | .ascii "Feature is now disabled\n" |
| | .endif |
| | </syntaxhighlight> |
| | ==== Verwendung von .elif ==== |
| | <syntaxhighlight lang="asm"> |
| | .equ MODE, 2 |
|
| |
|
| .unreq alias-name | | .if MODE == 1 |
| This undefines a register alias which was previously defined using the req, dn or qn directives. For example:
| | .ascii "Mode 1 enabled\n" |
| foo .req r0
| | .elif MODE == 2 |
| .unreq foo
| | .ascii "Mode 2 enabled\n" |
|
| | .else |
| An error occurs if the name is undefined. Note - this pseudo op can be used to delete builtin in register name aliases (eg 'r0'). This should only be done if it is really necessary.
| | .ascii "Unknown mode\n" |
| | .endif |
| | </syntaxhighlight> |
| | ==== Praktische Anwendungen für ARM64 ==== |
| | ===== Plattformübergreifende Assemblierung ===== |
| | Sie können spezifischen Code für verschiedene Prozessorarchitekturen schreiben. |
|
| |
|
| | <syntaxhighlight lang="asm"> |
| | .ifdef __aarch64__ |
| | // Code spezifisch für ARM64-Architektur |
| | .ascii "ARM64 architecture\n" |
| | .endif |
|
| |
|
| | .ifdef __x86_64__ |
| | // Code spezifisch für x86_64-Architektur |
| | .ascii "x86_64 architecture\n" |
| | .endif |
| | </syntaxhighlight> |
| | ==== Debugging und Optimierung ==== |
| | Aktivieren oder deaktivieren Sie Debugging-Code basierend auf definierten Symbolen. |
|
| |
|
| .unwind_raw offset, byte1, ...
| | <syntaxhighlight lang="asm"> |
| Insert one of more arbitary unwind opcode bytes, which are known to adjust the stack pointer by offset bytes.
| | .equ DEBUG, 1 |
| For example .unwind_raw 4, 0xb1, 0x01 is equivalent to .save {r0}
| |
|
| |
|
| | .if DEBUG |
| | // Debugging-Code |
| | .ascii "Debug mode active\n" |
| | .else |
| | // Produktionscode |
| | .ascii "Production mode active\n" |
| | .endif |
| | </syntaxhighlight> |
| | ==== Feature-Toggles ==== |
| | Sie können bestimmte Funktionen basierend auf definierten Symbolen aktivieren oder deaktivieren. |
|
| |
|
| .vsave vfp-reglist
| | <syntaxhighlight lang="asm"> |
| Generate unwinder annotations to restore the VFP registers in vfp-reglist using FLDMD. Also works for VFPv3 registers that are to be restored using VLDM. The format of vfp-reglist is the same as the corresponding store-multiple instruction.
| | .define FEATURE_X |
|
| |
| VFP registers
| |
|
| |
|
| .vsave {d8, d9, d10}
| | .ifdef FEATURE_X |
| fstmdd sp!, {d8, d9, d10}
| | // Code für Feature X |
|
| | .ascii "Feature X is enabled\n" |
| VFPv3 registers
| | .else |
| | // Alternativer Code |
| | .ascii "Feature X is disabled\n" |
| | .endif |
| | </syntaxhighlight> |
| | ==== Bedingte Assemblierung im ARM64-Assembler ==== |
| | Hier ist ein Beispiel, das verschiedene Aspekte der bedingten Assemblierung für ARM64-Assembler zeigt: |
|
| |
|
| .vsave {d15, d16, d17}
| | <syntaxhighlight lang="asm"> |
| vstm sp!, {d15, d16, d17}
| | .section .data |
|
| | .equ DEBUG, 1 |
| Since FLDMX and FSTMX are now deprecated, this directive should be used in favour of .save for saving VFP registers for ARMv6 and above.
| | .asciz "Debug: " |
|
| |
|
| === C to Assembly ===
| | .section .text |
| 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.
| | .globl _start |
| | |
| Just to illustrate using an example. Here’s a subroutine that does nothing useful.
| |
| | |
| #include <stdio.h>
| |
| | |
| 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
| |
| .file "calc.c"
| |
| .text
| |
| .align 2
| |
| .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: | | _start: |
| mov x8, __NR_write | | .if DEBUG |
| mov x2, hello_len | | ldr x0, =message_debug |
| adr x1, hello_txt
| | bl printf |
| mov x0, STDOUT_FILENO | | .else |
| svc 0 | | ldr x0, =message_production |
| | bl printf |
| | .endif |
|
| |
|
| mov x8, __NR_exit
| | // Exit system call |
| svc 0
| | mov x8, #93 |
| | mov x0, #0 |
| | svc #0 |
|
| |
|
| .data
| | message_debug: |
| | .asciz "Debug mode active\n" |
| | message_production: |
| | .asciz "Production mode active\n" |
| | </syntaxhighlight> |
| | Erklärung des Beispiels: |
| | .section .data: Definiert den Datenabschnitt, in dem die Zeichenketten gespeichert werden. |
| | .section .text: Definiert den Textabschnitt, in dem der ausführbare Code gespeichert wird. |
| | .globl _start: Markiert den Einstiegspunkt des Programms. |
| | .if DEBUG: Bedingte Assemblierung, die überprüft, ob DEBUG definiert und nicht null ist. |
| | ldr x0, =message_debug: Lädt die Adresse der Debug-Nachricht in das Register x0, wenn DEBUG definiert ist. |
| | bl printf: Ruft die printf-Funktion auf, um die Nachricht auszugeben. |
| | svc #0: Führt den svc (SuperVisor Call) Befehl aus, um das Programm zu beenden. |
| | Dieses Beispiel zeigt, wie bedingte Assemblierung verwendet werden kann, um unterschiedliche Nachrichten basierend auf dem Wert des DEBUG-Symbols auszugeben. |
|
| |
|
| hello_txt: .ascii "Hello, World!\n"
| | == Makros == |
| hello_len = . - hello_txt
| | '''.macro''' |
| Preprocess the above source using CPP -E. The result of this will be replacing each symbolic constant used with its assigned numeric value.
| |
|
| |
|
| | Mit den Befehlen '''.macro''' und '''.endm''' können Sie Makros definieren, die Assembly-Ausgaben generieren. Diese Definition gibt beispielsweise ein Makro an, '''sum''' das eine Zahlenfolge in den Speicher einfügt: |
| | <syntaxhighlight lang="asm"> |
| | .macro sum from=0, to=5 |
| | .long \from |
| | .if \to-\from |
| | sum "(\from+1)",\to |
| | .endif |
| | .endm |
| | </syntaxhighlight> |
|
| |
|
| | Mit dieser Definition, ''SUM 0,5'' ist gleichwertig mit dieser Assemblyeingabe: |
| | <syntaxhighlight lang="asm"> |
| | .long 0 |
| | .long 1 |
| | .long 2 |
| | .long 3 |
| | .long 4 |
| | .long 5 |
| | </syntaxhighlight> |
| | .macro macname |
| | .macro macname macargs … |
|
| |
|
| Finally, assemble using GAS and link with LD.
| | :Damit beginnt die Definition eines Makros namens ''macname'' . Wenn die Makrodefinition Argumente erfordert, werden nach dem Macronamen ihre Namen übergeben, die per Komma oder Leerzeichen definiert werden können. Jedem Argument kann ein Standardwert zugeordnet werden, indem dem Namen ein "= def" zugeordnet wird. |
| | :<syntaxhighlight lang="asm"> |
| | .macro comm |
| | </syntaxhighlight> |
| | ::Der Beginn eines Makros ohne Argumente. |
| | :<syntaxhighlight lang="asm"> |
| | .macro plus1 p, p1 |
| | .macro plus1 p p1 |
| | </syntaxhighlight> |
| | ::Erzeugt ein Makro namens plus1, das zwei Argumente annimmt. Beide Schreibweisen sind möglich. Um auf die Argumente innerhalb des Makros zuzugreifen wird \p oder \p1 verwendet. |
| | :<syntaxhighlight lang="asm"> |
| | .macro reserve_str p1=0 p2 |
| | </syntaxhighlight> |
| | ::Erzeugt ein Makro namens reserve_str, das zwei Argumente annimmt. Das erste Argument "p1" hat als Standardwert "0", wenn dieses nicht angegeben wurde. Wird ein Parameter übergeben, wird der Standardwert überschrieben. Um "nur" das zweite Argument "p2" anzusprechen wird das Makro wie volgt aufgerufen: |
| | ::<syntaxhighlight lang="asm"> |
| | reserve_str ,b |
| | </syntaxhighlight> |
| | :<syntaxhighlight lang="asm"> |
| | .macro m p1:req, p2=0, p3:vararg |
| | </syntaxhighlight> |
| | ::Erzeugt ein Makro namens m, das mind. drei Argumente annimmt. Das erste Argument muss mit übergeben werden. Hier wird das Schlüsselwort :req verwendet, welches besagt, dass ein Wert mit übergeben werden muss. Das zweite Argument kann übergeben werden, wenn dieser kein Argument enthält, wird der Standradwert "0" verwendet. Das dritte Argument bekommt alle Werte zugesprochen, die sonst noch angegeben wurden. Hier wird Schlüsselwort :vararg verwendet. |
| | <syntaxhighlight lang="asm"> |
| | .endm |
| | </syntaxhighlight> |
| | :Definiert das Ende des Makros. |
| | <syntaxhighlight lang="asm"> |
| | .exitm |
| | </syntaxhighlight> |
| | :Definiert ein vorzeitiges Ende des Makros. |
| | <syntaxhighlight lang="asm"> |
| | \@ (Backslash-At): |
| | </syntaxhighlight> |
| | :\@ ist eine Erweiterung, die eine eindeutige, numerische Kennung für jedes Auftreten eines Makros generiert. Diese Nummer ändert sich bei jedem Aufruf des Makros und kann daher verwendet werden, um eindeutige Namen für Labels, Register oder Variablen zu erstellen. |
| | :Die generierte Zahl ist immer gleich innerhalb desselben Makroaufrufs, aber sie ändert sich bei jedem neuen Makroaufruf. |
| | :Beispiel: |
| | :<syntaxhighlight lang="asm"> |
| | .macro UNIQUE_LABEL |
| | label_\@: |
| | .endm |
| | </syntaxhighlight> |
| | :Bei jedem Aufruf des Makros UNIQUE_LABEL wird label_\@ zu einem eindeutigen Label wie label_1, label_2 usw. |
| | <syntaxhighlight lang="asm"> |
| | \+ (Backslash-Plus): |
| | </syntaxhighlight> |
| | :\+ ist ein Zähler, der jedes Mal erhöht wird, wenn er verwendet wird. Anders als \@ bleibt \+ nicht auf den Bereich eines Makros beschränkt und kann über mehrere Makroaufrufe hinweg zunehmen. |
| | :Es kann nützlich sein, um eindeutig nummerierte Labels oder Variablen zu erstellen, die eine sequentielle Reihenfolge benötigen. |
| | :Beispiel: |
| | :<syntaxhighlight lang="asm"> |
| | .macro INCREMENT_LABEL |
| | label_\+: |
| | .endm |
| | </syntaxhighlight> |
| | :Bei jedem Aufruf des Makros INCREMENT_LABEL erhöht sich der Wert von \+ um eins, sodass die Labels aufeinanderfolgende Nummern wie label_0, label_1, label_2 usw. erhalten. |
|
| |
|
| | == Kommentare == |
|
| |
|
| | === Grundlegende Syntax === |
| | Einzeilige Kommentare beginnen mit einem //-Zeichen, @-Zeichen oder einem #-Zeichen. Alles, was nach diesem Zeichen folgt, wird vom Assembler ignoriert. |
| | <syntaxhighlight lang="asm"> |
| | // Dies ist ein Kommentar |
| | mov x0, #1 // Setzt den Wert 1 in das Register x0 |
| | </syntaxhighlight> |
| | Mehrzeilige Kommentare können, wie in C, auch mehrzeilig sein. Dazu wird der Kommentar zwischen den Zeichen /* und */ gesetzt. |
| | <syntaxhighlight lang="asm"> |
| | /* |
| | Dieser Text ist ein |
| | mehrzeiliger Kommentar |
| | */ |
| | </syntaxhighlight> |
| | ===Best Practices für Kommentare=== |
| | Kommentare sollten klar und prägnant sein. Sie sollten den Zweck des Codes erklären, ohne überflüssig zu sein. |
| | <syntaxhighlight lang="asm"> |
| | // Initialisiert das Register x0 mit dem Wert 1 |
| | mov x0, #1 |
| | </syntaxhighlight> |
| | Verwende die Kommentare, um komplexe oder nicht intuitive Teile des Codes zu erklären. |
| | <syntaxhighlight lang="asm"> |
| | // Berechnet die Summe der ersten 10 natürlichen Zahlen |
| | // und speichert das Ergebnis in x0 |
| | mov x0, #0 // Setzt x0 auf 0 |
| | mov x1, #1 // Setzt x1 auf 1 (Schleifenzähler) |
| | loop_start: |
| | add x0, x0, x1 // Addiert den Wert von x1 zu x0 |
| | add x1, x1, #1 // Erhöht den Wert von x1 um 1 |
| | cmp x1, #10 // Vergleicht x1 mit 10 |
| | ble loop_start // Springt zurück zu loop_start, wenn x1 <= 10 |
| | </syntaxhighlight> |
| | Notiere die Annahmen, die über den Zustand des Systems oder der Register. |
| | <syntaxhighlight lang="asm"> |
| | // Annahme: x2 enthält die Anzahl der zu verarbeitenden Elemente |
| | mov x0, #0 // Initialisiert den Summenzähler auf 0 |
| | </syntaxhighlight> |
| | Wenn bestimmte Teile des Codes wartungsintensiv sind oder besondere Aufmerksamkeit erfordern, solle das in Kommentaren vermerken werden. |
| | <syntaxhighlight lang="asm"> |
| | // Achtung: Dieser Codeabschnitt ist leistungskritisch |
| | // Optimierungen können erforderlich sein |
| | </syntaxhighlight> |
| | Verwendung von TODOs und FIXMEs: Markiere die Bereiche des Codes, die noch bearbeitet oder verbessert werden müssen. |
| | <syntaxhighlight lang="asm"> |
| | // TODO: Fehlerbehandlung hinzufügen |
| | mov x0, #1 |
| | </syntaxhighlight> |
| | Kommentare können auch beim Debugging hilfreich sein, indem sie den Status und Änderungen von Registern oder Speicheradressen dokumentieren: |
| | <syntaxhighlight lang="asm"> |
| | // Debugging: Überprüfen des Werts von x0 nach der Addition |
| | mov x0, #5 |
| | add x0, x0, #3 // x0 sollte jetzt 8 enthalten |
| | </syntaxhighlight> |
|
| |
|
| The following two directives are examples of simple text substitution or symbolic constants.
| | Halte Dich an eine konsistente Kommentierungsstrategie im gesamten Code, um die Lesbarkeit und Wartbarkeit zu erhöhen. |
|
| |
|
| #define FALSE 0
| | ----- |
| #define TRUE 1
| |
| The equivalent can be accomplished with the .EQU or .SET directives in GAS.
| |
|
| |
|
| .equ TRUE, 1
| | {| style="width: 100%; |
| .set TRUE, 1
| | | style="width: 33%;" | [[GNU Compiler Collection|< Zurück (GNU Compiler Collection)]] |
|
| | | style="width: 33%; text-align:center;" | [[Hauptseite|< Hauptseite >]] |
| .equ FALSE, 0
| | | style="width: 33%; text-align:right;" | [[GNU C Compiler|Weiter (GNU C Compiler) >]] |
| .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.
| |
Der GNU Assembler, häufig als GAS bezeichnet, ist ein Teil der GNU Binutils und ein wesentlicher Bestandteil der GNU Compiler Collection (GCC). Hier sind einige wichtige Punkte über den GNU Assembler:
- Zweck und Funktion: GAS ist ein Assembler, der Quellcode in Assemblersprache in Maschinencode übersetzt. Dies ist ein wichtiger Schritt im Kompilierungsprozess, insbesondere bei der Umwandlung von Hochsprachen wie C oder C++ in ausführbare Programme.
- Unterstützte Architekturen: GAS unterstützt eine Vielzahl von Prozessorarchitekturen, darunter x86, ARM, MIPS, PowerPC und viele mehr. Diese Vielseitigkeit macht es zu einem äußerst nützlichen Werkzeug für die Entwicklung auf verschiedenen Plattformen.
- Syntax: GAS verwendet eine spezielle Syntax, die als AT&T-Syntax bekannt ist. Diese unterscheidet sich von der Intel-Syntax, die in vielen anderen Assemblern verwendet wird. Zum Beispiel verwendet GAS das Format opcode source, destination, während die Intel-Syntax opcode destination, source verwendet.
- Integration mit GCC: Da GAS ein Teil der GNU Binutils ist, arbeitet es nahtlos mit GCC zusammen. Wenn Sie ein Programm mit GCC kompilieren, wird der Quellcode in der Regel zuerst in Assemblercode und dann in Maschinencode umgewandelt, wobei GAS diese Umwandlung übernimmt.
- Befehlszeilenoptionen: GAS bietet eine Vielzahl von Befehlszeilenoptionen, die es Entwicklern ermöglichen, die Assemblierung fein abzustimmen. Einige gängige Optionen sind -o zum Festlegen des Ausgabedateinamens und -g zum Hinzufügen von Debugging-Informationen.
- Lizenz: GAS wird unter der GNU General Public License (GPL) veröffentlicht. Dies bedeutet, dass es frei verfügbar ist und jeder den Quellcode einsehen, modifizieren und weiterverbreiten kann, solange die Bedingungen der GPL eingehalten werden.
Im folgenden beschreibe ich eine Auswahl von Präprozessor-Direktiven, die Präfixoperatoren und Infix-Operatoren. Zusätzlich gehe ich auf Bedingte Assemblierung und Makros ein.
Zusätzliche Themen zum GNU Assembler:
Präprozessor-Direktiven
Präprozessor-Direktiven
| Direktive |
Bedeutung
|
| .align Ausrichtung , Füllwert, MaxAusrichtung |
Ausrichtung bis zu einer bestimmten Speichergrenze.
- Optional: Füllwert gibt den Wert an, mit dem Aufgefüllt wird; MaxAusrichtung, wenn Ausrichtung höher wird, wird die Ausrichtung ignoriert.
- Ausrichtung: Anzahl der niederwertigen Nullbits, die der Zähler nach dem Fortschreiten haben muss. Zum Beispiel .align 3 erhöht den Standortzähler, bis er ein Vielfaches von 8 ist.
|
| .ascii "string"... |
.ascii erwartet kein oder mehrere durch Kommas getrennte Strings. Es fügt jeden String zu aufeinanderfolgenden Adressen zusammen. Die Strings werden nicht mit NULL beendet
|
| .asciz "string"... |
Wie .ascii, jedoch wird jedem String eine NULL angehägt, wie es zum Beispiel unter C üblich.
|
| .balign[wl] Ausrichtung, Füllwert, MaxAusrichtung |
Ausrichtung bis zu einer bestimmten Speichergrenze.
- Optional: Füllwert gibt den Wert an, mit dem Aufgefüllt wird; MaxAusrichtung, wenn Ausrichtung höher wird, wird die Ausrichtung ignoriert.
- Ausrichtung: Anzahl der Bytes, die der Zähler nach dem Fortschreiten haben muss. Zum Beispiel .balign 8 erhöht den Standortzähler, bis er ein Vielfaches von 8 ist. [w] richtet es nach Word-Länge (2 Bytes) aus und [l] als Longword (4 Bytes) aus.
|
| .byte Werte |
Erwartet Werte in Bytegröße, die durch Kommas getrennt sein können
|
| .data Unterabschnitt |
Daten werden in den Abschnitt ".data" abgelegt
|
| .double flonums |
Erwartet Gleitkommazahlen, die durch Kommas getrennt sein können
|
| .equ symbol, expression |
Definiert ein Symbol mit einem Wert (siehe auch .set)
|
| .float flonums |
Erwartet Gleitkommazahlen, die durch Kommas getrennt sein können
|
.global symbol .globl symbol |
Macht ein Symbol global bekannt
|
| .hword Werte |
Erwartet Werte in 16-Bit-Zahl-Wert, die durch Kommas getrennt sein können
|
| .include "file" |
Inkludiert eine Datei, die Code oder Daten beinhaltet. Der Inhalt wird an der Stelle des .include-Befehls eingefügt
|
| .int Werte |
Erwartet Werte, die durch Kommas getrennt sein können. Die Größe ist abhängig von der Kompilierungsumgebung
|
| .long Werte |
Wie .int
|
| .octa Werte |
Erwartet Werte in 16 bytes-Zahl-Wert, die durch Kommas getrennt sein können
|
| .org new-pos , fill |
Richtet den Code an die "new-pos" an. Mit "fill" (optional) wird angegeben, mit welchen Werten der übersprungen Speicher gefüllt wird
|
| .quad Werte |
Erwartet Werte in 8 bytes-Zahl-Wert, die durch Kommas getrennt sein können
|
| name .req registername |
Dadurch wird ein Alias für den Registernamen mit dem Namen "name" erstellt . Beispiel:
Register0 .req x0
|
| .set symbol, expression |
Definiert ein Symbol mit einem Wert
|
| .short Werte |
Erwartet Werte, die durch Kommas getrennt sein können. Die Größe ist abhängig von der Kompilierungsumgebung
|
| .single flonums |
Wie .float
|
| .space größe, fill |
belegt Speicher von "größe" gefüllt optional mit "fill"
|
| .string "str" |
Wie .asciz
|
| .unreq alias-name |
Dadurch wird ein Registeralias gelöscht, der zuvor mit der .req Direktive definiert wurde.
|
Präfixoperator
Präfixoperator
| - |
negiert den absolute Wert
|
| ~ |
Bitweise NOT vom absoluten Wert
|
Infix-Operatoren
Nach Priorität sortiert:
Infix-Operatoren
| * |
Multiplikation
|
| / |
Division
|
| % |
Rest
|
| << |
Bitweise nach links verschieben
|
| >> |
Bitweise nach rechts verschieben
|
| ǀ |
Bitweises Inklusives ODER
|
| & |
Bitweises UND
|
| ^ |
Bitweises Exklusiv-Oder
|
| ! |
Bitweise oder nicht
|
| + |
Addition
|
| - |
Subtraktion
|
| == |
Ist gleich
|
<> != |
Ist nicht gleich
|
| < |
Ist kleiner als
|
| > |
Ist größer als
|
| >= |
Ist größer als oder gleich
|
| <= |
Ist kleiner als oder gleich
|
| && |
Logisches UND
|
| ǀǀ |
Logisches ODER
|
Bedingte Assemblierung
Bedingte Assemblierung im GNU Assembler (GAS) ist eine mächtige Technik, die es ermöglicht, Teile des Assemblercodes basierend auf bestimmten Bedingungen einzuschließen oder auszuschließen. Dies ist besonders nützlich, wenn man für verschiedene Architekturen oder Konfigurationen, wie z.B. ARM64, entwickelt. Hier sind die wichtigsten Aspekte der bedingten Assemblierung im Zusammenhang mit ARM64-Assembler:
Grundlegende Direktiven für Bedingte Assemblierung
- .if / .else / .endif: Diese Direktiven werden verwendet, um Codeblöcke basierend auf einer Bedingung ein- oder auszuschließen.
- .ifdef / .ifndef: Diese Direktiven überprüfen, ob ein Symbol definiert ist oder nicht.
- .elif: Diese Direktive ermöglicht alternative Codepfade, wenn die ursprüngliche Bedingung nicht erfüllt ist.
- .define / .undef: Diese Direktiven definieren Symbole oder heben deren Definition auf.
Beispiele für Bedingte Assemblierung im ARM64-Assembler
Einfache Bedingte Assemblierung mit .if, .else und .endif
.equ DEBUG, 1 // Definiert ein Symbol DEBUG mit dem Wert 1
.if DEBUG
// Dieser Code wird assembliert, wenn DEBUG nicht null ist
.ascii "Debugging enabled\n"
.else
// Dieser Code wird assembliert, wenn DEBUG null ist
.ascii "Debugging disabled\n"
.endif
Bedingte Assemblierung mit .ifdef und .ifndef
#define FEATURE_ENABLED // Definiert ein Symbol FEATURE_ENABLED
.ifdef FEATURE_ENABLED
// Dieser Code wird assembliert, wenn FEATURE_ENABLED definiert ist
.ascii "Feature is enabled\n"
.else
// Dieser Code wird assembliert, wenn FEATURE_ENABLED nicht definiert ist
.ascii "Feature is disabled\n"
.endif
#undef FEATURE_ENABLED // Hebt die Definition von FEATURE_ENABLED auf
.ifndef FEATURE_ENABLED
// Dieser Code wird assembliert, wenn FEATURE_ENABLED nicht definiert ist
.ascii "Feature is now disabled\n"
.endif
Verwendung von .elif
.equ MODE, 2
.if MODE == 1
.ascii "Mode 1 enabled\n"
.elif MODE == 2
.ascii "Mode 2 enabled\n"
.else
.ascii "Unknown mode\n"
.endif
Praktische Anwendungen für ARM64
Plattformübergreifende Assemblierung
Sie können spezifischen Code für verschiedene Prozessorarchitekturen schreiben.
.ifdef __aarch64__
// Code spezifisch für ARM64-Architektur
.ascii "ARM64 architecture\n"
.endif
.ifdef __x86_64__
// Code spezifisch für x86_64-Architektur
.ascii "x86_64 architecture\n"
.endif
Debugging und Optimierung
Aktivieren oder deaktivieren Sie Debugging-Code basierend auf definierten Symbolen.
.equ DEBUG, 1
.if DEBUG
// Debugging-Code
.ascii "Debug mode active\n"
.else
// Produktionscode
.ascii "Production mode active\n"
.endif
Feature-Toggles
Sie können bestimmte Funktionen basierend auf definierten Symbolen aktivieren oder deaktivieren.
.define FEATURE_X
.ifdef FEATURE_X
// Code für Feature X
.ascii "Feature X is enabled\n"
.else
// Alternativer Code
.ascii "Feature X is disabled\n"
.endif
Bedingte Assemblierung im ARM64-Assembler
Hier ist ein Beispiel, das verschiedene Aspekte der bedingten Assemblierung für ARM64-Assembler zeigt:
.section .data
.equ DEBUG, 1
.asciz "Debug: "
.section .text
.globl _start
_start:
.if DEBUG
ldr x0, =message_debug
bl printf
.else
ldr x0, =message_production
bl printf
.endif
// Exit system call
mov x8, #93
mov x0, #0
svc #0
message_debug:
.asciz "Debug mode active\n"
message_production:
.asciz "Production mode active\n"
Erklärung des Beispiels:
.section .data: Definiert den Datenabschnitt, in dem die Zeichenketten gespeichert werden.
.section .text: Definiert den Textabschnitt, in dem der ausführbare Code gespeichert wird.
.globl _start: Markiert den Einstiegspunkt des Programms.
.if DEBUG: Bedingte Assemblierung, die überprüft, ob DEBUG definiert und nicht null ist.
ldr x0, =message_debug: Lädt die Adresse der Debug-Nachricht in das Register x0, wenn DEBUG definiert ist.
bl printf: Ruft die printf-Funktion auf, um die Nachricht auszugeben.
svc #0: Führt den svc (SuperVisor Call) Befehl aus, um das Programm zu beenden.
Dieses Beispiel zeigt, wie bedingte Assemblierung verwendet werden kann, um unterschiedliche Nachrichten basierend auf dem Wert des DEBUG-Symbols auszugeben.
Makros
.macro
Mit den Befehlen .macro und .endm können Sie Makros definieren, die Assembly-Ausgaben generieren. Diese Definition gibt beispielsweise ein Makro an, sum das eine Zahlenfolge in den Speicher einfügt:
.macro sum from=0, to=5
.long \from
.if \to-\from
sum "(\from+1)",\to
.endif
.endm
Mit dieser Definition, SUM 0,5 ist gleichwertig mit dieser Assemblyeingabe:
.long 0
.long 1
.long 2
.long 3
.long 4
.long 5
.macro macname
.macro macname macargs …
- Damit beginnt die Definition eines Makros namens macname . Wenn die Makrodefinition Argumente erfordert, werden nach dem Macronamen ihre Namen übergeben, die per Komma oder Leerzeichen definiert werden können. Jedem Argument kann ein Standardwert zugeordnet werden, indem dem Namen ein "= def" zugeordnet wird.
-
- Der Beginn eines Makros ohne Argumente.
.macro plus1 p, p1
.macro plus1 p p1
- Erzeugt ein Makro namens plus1, das zwei Argumente annimmt. Beide Schreibweisen sind möglich. Um auf die Argumente innerhalb des Makros zuzugreifen wird \p oder \p1 verwendet.
.macro reserve_str p1=0 p2
- Erzeugt ein Makro namens reserve_str, das zwei Argumente annimmt. Das erste Argument "p1" hat als Standardwert "0", wenn dieses nicht angegeben wurde. Wird ein Parameter übergeben, wird der Standardwert überschrieben. Um "nur" das zweite Argument "p2" anzusprechen wird das Makro wie volgt aufgerufen:
.macro m p1:req, p2=0, p3:vararg
- Erzeugt ein Makro namens m, das mind. drei Argumente annimmt. Das erste Argument muss mit übergeben werden. Hier wird das Schlüsselwort :req verwendet, welches besagt, dass ein Wert mit übergeben werden muss. Das zweite Argument kann übergeben werden, wenn dieser kein Argument enthält, wird der Standradwert "0" verwendet. Das dritte Argument bekommt alle Werte zugesprochen, die sonst noch angegeben wurden. Hier wird Schlüsselwort :vararg verwendet.
- Definiert das Ende des Makros.
- Definiert ein vorzeitiges Ende des Makros.
- \@ ist eine Erweiterung, die eine eindeutige, numerische Kennung für jedes Auftreten eines Makros generiert. Diese Nummer ändert sich bei jedem Aufruf des Makros und kann daher verwendet werden, um eindeutige Namen für Labels, Register oder Variablen zu erstellen.
- Die generierte Zahl ist immer gleich innerhalb desselben Makroaufrufs, aber sie ändert sich bei jedem neuen Makroaufruf.
- Beispiel:
.macro UNIQUE_LABEL
label_\@:
.endm
- Bei jedem Aufruf des Makros UNIQUE_LABEL wird label_\@ zu einem eindeutigen Label wie label_1, label_2 usw.
- \+ ist ein Zähler, der jedes Mal erhöht wird, wenn er verwendet wird. Anders als \@ bleibt \+ nicht auf den Bereich eines Makros beschränkt und kann über mehrere Makroaufrufe hinweg zunehmen.
- Es kann nützlich sein, um eindeutig nummerierte Labels oder Variablen zu erstellen, die eine sequentielle Reihenfolge benötigen.
- Beispiel:
.macro INCREMENT_LABEL
label_\+:
.endm
- Bei jedem Aufruf des Makros INCREMENT_LABEL erhöht sich der Wert von \+ um eins, sodass die Labels aufeinanderfolgende Nummern wie label_0, label_1, label_2 usw. erhalten.
Kommentare
Grundlegende Syntax
Einzeilige Kommentare beginnen mit einem //-Zeichen, @-Zeichen oder einem #-Zeichen. Alles, was nach diesem Zeichen folgt, wird vom Assembler ignoriert.
// Dies ist ein Kommentar
mov x0, #1 // Setzt den Wert 1 in das Register x0
Mehrzeilige Kommentare können, wie in C, auch mehrzeilig sein. Dazu wird der Kommentar zwischen den Zeichen /* und */ gesetzt.
/*
Dieser Text ist ein
mehrzeiliger Kommentar
*/
Best Practices für Kommentare
Kommentare sollten klar und prägnant sein. Sie sollten den Zweck des Codes erklären, ohne überflüssig zu sein.
// Initialisiert das Register x0 mit dem Wert 1
mov x0, #1
Verwende die Kommentare, um komplexe oder nicht intuitive Teile des Codes zu erklären.
// Berechnet die Summe der ersten 10 natürlichen Zahlen
// und speichert das Ergebnis in x0
mov x0, #0 // Setzt x0 auf 0
mov x1, #1 // Setzt x1 auf 1 (Schleifenzähler)
loop_start:
add x0, x0, x1 // Addiert den Wert von x1 zu x0
add x1, x1, #1 // Erhöht den Wert von x1 um 1
cmp x1, #10 // Vergleicht x1 mit 10
ble loop_start // Springt zurück zu loop_start, wenn x1 <= 10
Notiere die Annahmen, die über den Zustand des Systems oder der Register.
// Annahme: x2 enthält die Anzahl der zu verarbeitenden Elemente
mov x0, #0 // Initialisiert den Summenzähler auf 0
Wenn bestimmte Teile des Codes wartungsintensiv sind oder besondere Aufmerksamkeit erfordern, solle das in Kommentaren vermerken werden.
// Achtung: Dieser Codeabschnitt ist leistungskritisch
// Optimierungen können erforderlich sein
Verwendung von TODOs und FIXMEs: Markiere die Bereiche des Codes, die noch bearbeitet oder verbessert werden müssen.
// TODO: Fehlerbehandlung hinzufügen
mov x0, #1
Kommentare können auch beim Debugging hilfreich sein, indem sie den Status und Änderungen von Registern oder Speicheradressen dokumentieren:
// Debugging: Überprüfen des Werts von x0 nach der Addition
mov x0, #5
add x0, x0, #3 // x0 sollte jetzt 8 enthalten
Halte Dich an eine konsistente Kommentierungsstrategie im gesamten Code, um die Lesbarkeit und Wartbarkeit zu erhöhen.