mirror of
https://github.com/pkivolowitz/asm_book.git
synced 2026-06-21 02:06:48 +08:00
added short chapter on variadic functions
This commit is contained in:
parent
69c2a4c465
commit
f3f3f84b3c
5 changed files with 153 additions and 0 deletions
|
|
@ -303,6 +303,7 @@ What would a book about assembly language be without bit bashing?
|
|||
| --- | [Under the hood: System Calls](./more/system_calls/README.md) | [Link](./more/system_calls/README.pdf) |
|
||||
| --- | [Apple Silicon](./more/apple_silicon/README.md) | [Link](./more/apple_silicon/README.pdf) |
|
||||
| --- | [Apple / Linux Convergence](./macros) | [Link](./macros/README.pdf) |
|
||||
| --- | [Variadic Functions](./more/varargs/README.md) | [Link](./more/varargs/README.pdf) |
|
||||
|
||||
## Macro Suite
|
||||
|
||||
|
|
|
|||
BIN
README.pdf
BIN
README.pdf
Binary file not shown.
69
more/varargs/README.md
Normal file
69
more/varargs/README.md
Normal file
|
|
@ -0,0 +1,69 @@
|
|||
# varargs
|
||||
|
||||
Mentioned elsewhere, how Apple and Linux handle variadic functions are
|
||||
quite different. In this chapter we will explore how variadic functions
|
||||
work by looking at an example in C++. Then, we'll review the differences
|
||||
between Apple and Linux.
|
||||
|
||||
## How Variadic Functions Determine Number of Parameters
|
||||
|
||||
The TL;DR is that something among the *initial* arguments tells the
|
||||
function how many other arguments to expect. Commonly, it is the very
|
||||
first argument that contains this information but it is possible to
|
||||
encode this information in a parameter other than the first as long as
|
||||
no optional parameter precedes it.
|
||||
|
||||
For example, take our friend and yours: `printf`. The first argument is
|
||||
a template or formatting string that may contain zero or more instances
|
||||
of placeholders. For example:
|
||||
|
||||
| Sample | Expected Arguments |
|
||||
| ------ | ------------------ |
|
||||
| `printf("Foo");` | None |
|
||||
| `printf("%%d");` | None - notice the double % |
|
||||
| `printf("%d %ld");` | An int and a long |
|
||||
| `printf("%p %f");` | A pointer and a double |
|
||||
|
||||
The `printf` function figures out how many arguments to expect and what
|
||||
types to expect from the first parameter.
|
||||
|
||||
## Writing Variadic Functions in C or C++
|
||||
|
||||
You'll need to include `cstdarg` in C++ or `stdarg.h` in C++ or C.
|
||||
|
||||
Then, you'll use the **macros** `va_start()` and `va_end()` plus a
|
||||
variable of type `va_list`.
|
||||
|
||||
* `va_start()` is used to initialize the `va_list` variable with the
|
||||
address of the last required argument. This enables the variadic support
|
||||
functions to find the variadic parameters on the stack or in registers.
|
||||
|
||||
* `va_arg()`, another macro fetches the next variadic argument. The type
|
||||
of the argument is given as a parameter to `va_arg()`.
|
||||
|
||||
* `va_end()` is used to clean up the internal data structure allocated
|
||||
for you, behind your back.
|
||||
|
||||
Take a look at `Simple()` in [this source code](./main.cpp).
|
||||
|
||||
For a more sophisticated example, look at `LessSimple()` in the same
|
||||
file for something closer to `printf()`.
|
||||
|
||||
The above referenced source code contains ample commenting to explain
|
||||
what it is doing.
|
||||
|
||||
## Differences Between Apple and Linux
|
||||
|
||||
### Linux
|
||||
|
||||
Linux uses the first 8 scratch registers as normal. If you need to
|
||||
specify more than 8 arguments, the ninth and beyond go on the stack. It
|
||||
is up to you to determine how many arguments to expect (which you must
|
||||
do anyway) to determine where to find them.
|
||||
|
||||
### Apple
|
||||
|
||||
Apple puts the first argument in `x0` as usual but all remaining
|
||||
arguments go on the stack in right to left order. There are some
|
||||
other restrictions - it is best to refer to [this](https://developer.apple.com/documentation/xcode/writing-arm64-code-for-apple-platforms
|
||||
) cryptically written document.
|
||||
BIN
more/varargs/README.pdf
Normal file
BIN
more/varargs/README.pdf
Normal file
Binary file not shown.
83
more/varargs/main.cpp
Normal file
83
more/varargs/main.cpp
Normal file
|
|
@ -0,0 +1,83 @@
|
|||
#include <iostream>
|
||||
#include <iomanip>
|
||||
#include <cinttypes>
|
||||
#include <string>
|
||||
#include <cstdarg>
|
||||
#include <math.h>
|
||||
|
||||
using namespace std;
|
||||
|
||||
/* va_start() and va_end() are MACROS not functions themselves. The implementation
|
||||
of these are architecture dependent. A common implementation is that va_start()
|
||||
takes the address of the last non-variadic variable which identifies where on
|
||||
the stack the following arguments can be found. The format string (or whatever)
|
||||
determines how to advance the pointer. Some architectures are quite complicated.
|
||||
|
||||
For example, here is a comment from the implementation of va_start:
|
||||
//
|
||||
* In GNU the stack is not necessarily arranged very neatly in order to
|
||||
* pack shorts and such into a smaller argument list. Fortunately a
|
||||
* neatly arranged version is available through the use of __builtin_next_arg.
|
||||
//
|
||||
*/
|
||||
|
||||
/* Simple() - a first example of a variadic function in which all
|
||||
parameters are assumed to be pointers to char.
|
||||
*/
|
||||
|
||||
void Simple(uint32_t count, ...) {
|
||||
int digits_needed = (int) ceil(log10((double) count)) + 1;
|
||||
va_list args;
|
||||
va_start(args, count);
|
||||
for (uint32_t index = 0; index < count; index++) {
|
||||
cout << "Index: [" << setw(digits_needed) << index << "] " << va_arg(args, char *) << endl;
|
||||
}
|
||||
va_end(args);
|
||||
}
|
||||
|
||||
/* LessSimple() is a bit more like printf in that multiple data types are
|
||||
supported, passed in in a format string. This function is an important
|
||||
lesson in the types of data types that are supported by varargs. Note
|
||||
that a float is passed as a double and char and short are passed by as
|
||||
ints. The patterns is that if a data type is automatically promotable,
|
||||
you must handle it as the highest level of promotion.
|
||||
|
||||
For example, a char is an int so it must be handled like an int. Floats
|
||||
are automatically promotable to double, so they must be supported as
|
||||
doubles. An int is not automatically promoted to long, so longs can
|
||||
be dealt with directly.
|
||||
*/
|
||||
|
||||
void LessSimple(string fmt, ...) {
|
||||
va_list args;
|
||||
va_start(args, fmt);
|
||||
for (uint32_t index = 0; index < fmt.size(); index++) {
|
||||
switch (fmt.at(index)) {
|
||||
case 'i':
|
||||
cout << "i: " << va_arg(args, int64_t) << endl;
|
||||
break;
|
||||
case 'f':
|
||||
cout << "f: " << (float) va_arg(args, double) << endl;
|
||||
break;
|
||||
case 'd':
|
||||
cout << "d: " << va_arg(args, double) << endl;
|
||||
break;
|
||||
case 'c':
|
||||
cout << "c: " << (int8_t) va_arg(args, int32_t) << endl;
|
||||
break;
|
||||
case 's':
|
||||
cout << "s: " << (int16_t) va_arg(args, int32_t) << endl;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
va_end(args);
|
||||
}
|
||||
|
||||
int main() {
|
||||
Simple(5, "Foo", "is", "a", "Variadic", "Function");
|
||||
Simple(13, "Foo", "is", "a", "Variadic", "Function", "which means", "it", "can", "take", "any", "number", "of", "args");
|
||||
LessSimple(string("ifdcs"), 99L, 4.4f, 4.4, 'c', (int16_t) 30000);
|
||||
return 0;
|
||||
}
|
||||
Loading…
Reference in a new issue