diff --git a/README.md b/README.md index 1a1ed43..38c67aa 100644 --- a/README.md +++ b/README.md @@ -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 diff --git a/README.pdf b/README.pdf index a57adf1..dd70b55 100644 Binary files a/README.pdf and b/README.pdf differ diff --git a/more/varargs/README.md b/more/varargs/README.md new file mode 100644 index 0000000..fef771a --- /dev/null +++ b/more/varargs/README.md @@ -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. diff --git a/more/varargs/README.pdf b/more/varargs/README.pdf new file mode 100644 index 0000000..642d5b2 Binary files /dev/null and b/more/varargs/README.pdf differ diff --git a/more/varargs/main.cpp b/more/varargs/main.cpp new file mode 100644 index 0000000..6665bb6 --- /dev/null +++ b/more/varargs/main.cpp @@ -0,0 +1,83 @@ +#include +#include +#include +#include +#include +#include + +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; +}