Format fixes (+ add links) in 2004/vik2/README.md
This commit is contained in:
1 parent
6aa1141e29
commit
ca56f68cb4
1 file changed
+116
-97
+116
-97
@@ -1,11 +1,9 @@
|
||||
# Best Abuse of CPP
|
||||
|
||||
Daniel Vik
|
||||
926 Genter Street
|
||||
La Jolla, Ca 92037
|
||||
US
|
||||
|
||||
http://www.vik.cc/
|
||||
Daniel Vik
|
||||
US
|
||||
<https://www.vik.cc/>
|
||||
<http://www.danielvik.com>
|
||||
|
||||
## To build:
|
||||
|
||||
@@ -27,174 +25,194 @@ echo "Do or do not. There is no try."
|
||||
|
||||
## Judges' remarks:
|
||||
|
||||
The fun thing about CPP abuses is that, in most cases, running the
|
||||
preprocessor on them doesn't help you decipher the obfuscated code.
|
||||
That's assuming your preprocessor can take it at all.
|
||||
The fun thing about [CPP](https://en.wikipedia.org/wiki/C_preprocessor) abuses
|
||||
is that, in most cases, running the
|
||||
[preprocessor](https://en.wikipedia.org/wiki/C_preprocessor) on them doesn't
|
||||
help you decipher the obfuscated code. That's assuming your preprocessor can
|
||||
take it at all.
|
||||
|
||||
## Author's remarks:
|
||||
|
||||
### Introduction
|
||||
|
||||
It's been a while since the last pre-processor killer won the ioccc.
|
||||
I thought it was time to relive hour long compile times so I made a
|
||||
program that does (almost) all computation in the pre-processor. The
|
||||
program even causes gcc (and other pre-processors) to crash when
|
||||
configured to do maximum number of computations.
|
||||
The default configuration of the makefile is set to make the program
|
||||
compile in a not too long time.
|
||||
It's been a while since the last
|
||||
[pre-processor](https://en.wikipedia.org/wiki/C_preprocessor) killer won the
|
||||
IOCCC. I thought it was time to relive hour long compile times so I made a
|
||||
program that does (almost) all computation in the pre-processor. The program
|
||||
even causes `gcc` (and other pre-processors) to crash when configured to do
|
||||
maximum number of computations. The default configuration of the
|
||||
[Makefile](Makefile) is set to make the program compile in a not too long time.
|
||||
|
||||
I've got cpp to crash in a couple of diffenent ways. Most common is to
|
||||
get a segmentation fault, but a couple of times it just got stuck in
|
||||
I've got `cpp` to crash in a couple of different ways. Most common is to
|
||||
get a segmentation fault, but a couple of times it just got stuck in
|
||||
some infinite loop.
|
||||
|
||||
I actually had to write my own pre-processor to verify that the program
|
||||
was doing the right thing...
|
||||
I actually had to write my own
|
||||
[pre-processor](https://en.wikipedia.org/wiki/C_preprocessor) to verify that the
|
||||
program was doing the right thing...
|
||||
|
||||
Since it takes a very long time to compile the program, there is a
|
||||
symbol that can be defined to shorten the compile time and avoid
|
||||
Since it takes a very long time to compile the program, there is a
|
||||
symbol that can be defined to shorten the compile time and avoid
|
||||
compiler errors.
|
||||
|
||||
### Features
|
||||
|
||||
The program implements an 11-bit ALU in the pre-processor. The ALU has
|
||||
two registers, X and Y, for calculations and one status register SR.
|
||||
Each bit in the registers are represented by one symbol that are either
|
||||
defined or undefined.
|
||||
The program implements an 11-bit
|
||||
[ALU](https://en.wikipedia.org/wiki/Arithmetic_logic_unit) in the
|
||||
[pre-processor](https://en.wikipedia.org/wiki/C_preprocessor). The ALU has two
|
||||
[registers](https://en.wikipedia.org/wiki/Processor_register), `X` and `Y`, for
|
||||
calculations and one status register `SR`. Each bit in the registers are
|
||||
represented by one symbol that are either defined or undefined.
|
||||
|
||||
The ALU has six instructions:
|
||||
The ALU has six instructions:
|
||||
|
||||
CLR_X - Clear register X
|
||||
- `CLR_X` - Clear [register](https://en.wikipedia.org/wiki/Processor_register) `X`.
|
||||
|
||||
CLR_Y - Clear register Y
|
||||
- `CLR_Y` - Clear [register](https://en.wikipedia.org/wiki/Processor_register) `Y`.
|
||||
|
||||
ADD - Calculates X + Y and stores the result in X. If the result
|
||||
is larger than 11 bits, the CARRY bit in SR is set otherwise
|
||||
the CARRY bit is cleared.
|
||||
- `ADD` - Calculates `X` + `Y` and stores the result in `X`. If the result
|
||||
is larger than 11 bits, the [CARRY
|
||||
bit](https://en.wikipedia.org/wiki/Carry_flag) in `SR` is set otherwise
|
||||
the `CARRY` bit is cleared.
|
||||
|
||||
SUB - Calculates X - Y and stores the result in X. If Y was larger
|
||||
than X, the CARRY bit in SR is set otherwise the CARRY bit
|
||||
is cleared.
|
||||
- `SUB` - Calculates `X` - `Y` and stores the result in `X`. If `Y` was larger
|
||||
than `X`, the [CARRY bit](https://en.wikipedia.org/wiki/Carry_flag)
|
||||
in `SR` is set otherwise the `CARRY` bit is cleared.
|
||||
|
||||
TST - If X == Y the EQ bit in SR is set, otherwise the EQ bit is
|
||||
cleared. If X < Y the LT bit in SR is set, othervise the LT
|
||||
- `TST` - If `X` == `Y` the `EQ` bit in `SR` is set, otherwise the `EQ` bit is
|
||||
cleared. If `X` < `Y` the `LT` bit in `SR` is set, otherwise the `LT`
|
||||
bit is cleared.
|
||||
|
||||
INC2 - Calculates X + 2 and stores the result in X. The CARRY bit
|
||||
is handled as in the ADD instruction.
|
||||
- `INC2` - Calculates `X` + 2 and stores the result in `X`. The [CARRY
|
||||
bit](https://en.wikipedia.org/wiki/Carry_flag) is handled as in the
|
||||
`ADD` instruction.
|
||||
|
||||
Internally the ALU implements a one bit adder with a carry in and carry
|
||||
out. This adder is the core of the ALU. The ADD instruction uses the one
|
||||
bit adder to create the 11-bit adder. The SUB instruction is implemented
|
||||
by adding ~Y to X and then add 1. Finally the TST instruction calculates
|
||||
X-Y and checks the result and the CARRY bit in the SR register to set
|
||||
the EQ and LT bits.
|
||||
Internally the [ALU](https://en.wikipedia.org/wiki/Arithmetic_logic_unit)
|
||||
implements a one bit adder with a carry in and carry out. This adder is the core
|
||||
of the ALU. The `ADD` instruction uses the one bit adder to create the 11-bit
|
||||
adder. The SUB instruction is implemented by adding `~Y` to `X` and then add 1.
|
||||
Finally the `TST` instruction calculates `X-Y` and checks the result and the
|
||||
[CARRY bit](https://en.wikipedia.org/wiki/Carry_flag) in the `SR`
|
||||
[register](https://en.wikipedia.org/wiki/Processor_register) to set the `EQ` and
|
||||
`LT` bits.
|
||||
|
||||
All calculations are made using only the #ifdef, #infdef, #else, #endif,
|
||||
#define and #undef. I never assign a value to a defined symbol. The
|
||||
logic is done by assuming that a defined symbol is 1 and an undefined
|
||||
All calculations are made using only `#ifdef`, `#ifndef`, `#else`, `#endif`,
|
||||
`#define` and `#undef`. I never assign a value to a defined symbol. The
|
||||
logic is done by assuming that a defined symbol is 1 and an undefined
|
||||
symbol is 0. (I guess this means that I have unlimited memory as long as
|
||||
I only store zeroes (!)).
|
||||
|
||||
The application that uses the pre-processor ALU is a prime number
|
||||
generator. The application uses the same pre-processor directives.
|
||||
Needed variables are stored in memory represended by one defined or
|
||||
undefined symbol per bit. When doing arithmetics, the variables are
|
||||
loaded into the X and Y registers by defining / undefining the X and Y
|
||||
register bits. Then the calcualtion is made and the result is moved back
|
||||
to the memory.
|
||||
The application that uses the
|
||||
[pre-processor](https://en.wikipedia.org/wiki/C_preprocessor)
|
||||
[ALU](https://en.wikipedia.org/wiki/Arithmetic_logic_unit) is a [prime
|
||||
number](https://en.wikipedia.org/wiki/Prime_number)
|
||||
generator. The application uses the same pre-processor directives.
|
||||
Needed variables are stored in memory represented by one defined or
|
||||
undefined symbol per bit. When doing arithmetics, the variables are
|
||||
loaded into the `X` and `Y` registers by defining / undefining the `X` and `Y`
|
||||
[register](https://en.wikipedia.org/wiki/Processor_register) bits. Then the
|
||||
calculation is made and the result is moved back to the memory.
|
||||
|
||||
The choice of application may be a bit unfortunate since we've already
|
||||
seen a winning entry calculating prime nubmers in the pre-processor.
|
||||
There is a big difference though. The 1988 applin entry uses pre-
|
||||
processor arithmethic (in a very nice way though). This program only
|
||||
defines and undefines symbols in a well defined ;) way and doesn't do
|
||||
The choice of application may be a bit unfortunate since we've already
|
||||
seen a winning entry calculating [prime
|
||||
numbers](https://en.wikipedia.org/wiki/Prime_number) in the
|
||||
[pre-processor](https://en.wikipedia.org/wiki/C_preprocessor).
|
||||
There is a big difference though. The 1988 applin entry uses pre-
|
||||
processor arithmetic (in a very nice way though). This program only
|
||||
defines and undefines symbols in a well defined ;) way and doesn't do
|
||||
any pre-processor arithmetics.
|
||||
|
||||
The reason why I choosed to do a prime number application is that the
|
||||
algorithm can be written to do a lot of calculations without a lot of
|
||||
nesting (gnu cpp allows only 200 nested includes).
|
||||
The reason why I chose to do a [prime
|
||||
number](https://en.wikipedia.org/wiki/Prime_number) application is that the
|
||||
algorithm can be written to do a lot of calculations without a lot of nesting
|
||||
(`gnu cpp` allows only 200 nested includes).
|
||||
|
||||
### Build and Run
|
||||
|
||||
First, do a first pre processing of the initial definitions. The FNAME
|
||||
symbol must be defined to the name of the output file. If you want to
|
||||
speed up the compilation, define the STOP symbol to anything from _3 to
|
||||
_10. (The default value is _10 which will make the compilation take some
|
||||
time.) Prime numbers up to 2^STOP will be calculated.
|
||||
First, do a first pre-processing of the initial definitions. The FNAME
|
||||
symbol must be defined to the name of the output file. If you want to
|
||||
speed up the compilation, define the `STOP` symbol to anything from `_3` to
|
||||
`_10`. (The default value is `_10` which will make the compilation take some
|
||||
time.) Prime numbers up to `2^STOP` will be calculated.
|
||||
|
||||
```sh
|
||||
cpp '-DSTOP=_5' '-DFNAME="tmp1.c"' prog.c tmp1.c
|
||||
```
|
||||
|
||||
Then use cpp or any other pre-processor to do the actual work. This will
|
||||
take some time....
|
||||
Then use `cpp` or any other
|
||||
[pre-processor](https://en.wikipedia.org/wiki/C_preprocessor) to do the actual
|
||||
work. This will take some time....
|
||||
|
||||
```sh
|
||||
cpp tmp1.c > tmp2.c
|
||||
```
|
||||
|
||||
Finally compile the pre processed file
|
||||
Finally compile the pre-processed file
|
||||
|
||||
```sh
|
||||
cc -O tmp2.c -o prog
|
||||
```
|
||||
|
||||
Then run the program to reveil all prime numbers up to 32 (if you used
|
||||
STOP=_5 as above):
|
||||
Then run the program to reveal all prime numbers up to 32 (if you used
|
||||
`STOP=_5` as above):
|
||||
|
||||
```sh
|
||||
./prog
|
||||
./vik2
|
||||
```
|
||||
|
||||
### Verification
|
||||
|
||||
I wasn't able to compile the full program which calculates prime numbers
|
||||
up to 1024 using gnu cpp (or any other pre-processor) so I had to write
|
||||
my own. Knowing how the program works it was quite easy to reduce the
|
||||
compilation time from several hours to about one second. I also added
|
||||
some statistics to my home made pre-processor and I found that
|
||||
calculating prime numbers up to 1024 makes the program include itself
|
||||
over 6.8 million times (see the statistics table below).
|
||||
I wasn't able to compile the full program which calculates [prime
|
||||
numbers](https://en.wikipedia.org/wiki/Prime_number) up to 1024 using `gnu cpp`
|
||||
(or any other [pre-processor](https://en.wikipedia.org/wiki/C_preprocessor)) so
|
||||
I had to write my own. Knowing how the program works it was quite easy to reduce
|
||||
the compilation time from several hours to about one second. I also added some
|
||||
statistics to my home made pre-processor and I found that calculating prime
|
||||
numbers up to 1024 makes the program include itself over 6.8 million times (see
|
||||
the statistics table below).
|
||||
|
||||
### Obfuscation
|
||||
|
||||
Well, since 98.5% of the program is preprocessor directives, the main
|
||||
obfuscation is the use of preprocessor directives.
|
||||
Well, since 98.5% of the program is
|
||||
[preprocessor](https://en.wikipedia.org/wiki/C_preprocessor) directives, the
|
||||
main obfuscation is the use of preprocessor directives.
|
||||
|
||||
Doing arithmetics without acutally using any arithmetic operations makes
|
||||
Doing arithmetics without actually using any arithmetic operations makes
|
||||
the program quite hard to follow.
|
||||
|
||||
In order to actually do the calculations, the program includes itself a
|
||||
In order to actually do the calculations, the program includes itself a
|
||||
lot.
|
||||
|
||||
The symbol names are made as short as possible to meet the space limits.
|
||||
This of course adds to the obfuscation (even though the program is
|
||||
This of course adds to the obfuscation (even though the program is
|
||||
quite obfuscated with longer, readable macro names.
|
||||
|
||||
Finally, I made the 1.5% c code a bit harder to read by breaking all
|
||||
Finally, I made the 1.5% C code a bit harder to read by breaking all
|
||||
lines after three characters.
|
||||
|
||||
The program has 1029 lines which makes it the longest entry (so far).
|
||||
|
||||
Even though the pre-processed and indented output is easier to read, it
|
||||
Even though the pre-processed and indented output is easier to read, it
|
||||
is quite hard to find the code in it. The size of the pre-processed file
|
||||
is several Mb when using gnu cpp.
|
||||
is several Mb when using `gnu cpp`.
|
||||
|
||||
### Limitations
|
||||
|
||||
In order to fit the program within the space limits, I had to define the
|
||||
#define, #endif, and some other directives as single character symbols.
|
||||
This does not compile on all pre-processors but using gnu cpp as a first
|
||||
compilation step solves this.
|
||||
`#define`, `#endif`, and some other directives as single character symbols.
|
||||
This does not compile on all
|
||||
[pre-processors](https://en.wikipedia.org/wiki/C_preprocessor) but using `gnu
|
||||
cpp` as a first compilation step solves this.
|
||||
|
||||
### Statistics
|
||||
|
||||
The table below contains some interesting statistics about the
|
||||
compilation of the program. The intermediate file size is the size of
|
||||
the output file of the cpp pre-processor. The time taken to pre process
|
||||
the program were measured using gnu cpp on my 2.0MHz P4 with 512Mb ram
|
||||
and a lot of disk.
|
||||
The table below contains some interesting statistics about the compilation of
|
||||
the program. The intermediate file size is the size of the output file of the
|
||||
cpp [pre-processor](https://en.wikipedia.org/wiki/C_preprocessor). The time
|
||||
taken to pre-process the program were measured using gnu cpp on my 2.0MHz P4
|
||||
with 512Mb ram and a lot of disk.
|
||||
|
||||
```
|
||||
Generated Recursive Max nest Intermediate
|
||||
Level prime nrs includes count file size Time
|
||||
|
||||
@@ -208,6 +226,7 @@ and a lot of disk.
|
||||
_10 < 1024 6 826 389 114 63 782 912(*) 2h 47'58"(*)
|
||||
|
||||
(*) Before cpp crashed.
|
||||
```
|
||||
|
||||
## Copyright and CC BY-SA 4.0 License:
|
||||
|
||||
|
||||
Reference in new issue
Block a user