2025-06-26: Exam Embedded Computing 1#
Below you find descriptions of “situations”, like the code of a program that you are supposed to analyse, together with an explanation.
After each such situation description you see a table of “statements”, where each row has four fields.
Statement: a statement about the situation. The statement can either be true or false.
True: check that field with an ‘x’ if you think the statement is true.
False: check that field with an ‘x’ if you think the statement is false.
Why: This field is optional. If you are unsure about your answer, write a few words explaining why you think your answer is correct. If your reasoning is not complete nonsense, /me might diverge a little from his pure boolean “correct/incorrect” grading decision.
Not applying a ‘x’ to any of the “True” and “False” fields in a single row will render your answer incorrect, obviously, as does checking both fields.
No computers, no cellphones, no AI glasses, no earpods, no internet! Only pen and paper.
Grading algorithm
Inside one situation,
Each row counts as 1
The statement’s outcome is calculated as
The sum of correct rows
Minus the sum of incorrect rows
Globally,
Each situation has a weight attached (situations, together with their statements, are not equal in their difficulty)
The sum of the weighted situation outcomes is then scaled up to 40% (the theoretical part) as per syllabus
Stray Pointers#
Say you compile and run the program below, which is obviously buggy. Consider how large the resulting suffering can become.
int main()
{
int* address = (int*)666;
*address = 7;
return 0;
}
Statement |
True |
False |
Why |
|---|---|---|---|
The incident is logged, you are logged out of your computer immediately, and you may never log in again. |
. |
. |
|
The program is compiled and run on the Raspberry Pi. The designers of that hardware platform show some kind of humor that is prevalent among the British: the GPIO hardware inside the Pi SoC has its register map starting at address 666. Depending on the value that is written to that base address, you might end up shorting 3V3 to ground on pin 25. |
. |
. |
|
The OS intercepts a hardware exception from the Memory
Management Unit (MMU) which says, “At |
. |
. |
|
A fellow process on the system might have rightfully allocated
memory at position |
. |
. |
CPU Time#
Compared to the program from Stray Pointers, the program below is not exactly buggy in that sense, but misbehaves in a certain way.
int main()
{
while (true) {}
return 0;
}
Statement |
True |
False |
Why |
|---|---|---|---|
On a single-processor system, a process running that program’s code will never release the processor, and no other process will ever have a chance to run again. |
. |
. |
|
On a single-processor system, the OS kernel will interrupt the rogue process after a short time (a so-called time-slice), put it on a queue of “runnable” (but not running) processes, and give another process from that queue a chance to run. |
. |
. |
|
On a multi-processor (say, N) system, when you run N instances of the program, the entire system will come to a halt because each processor is saturated with one such idiot. |
. |
. |
|
On a multi-processor system, the OS will increase the CPU frequency to satisfy the needs of its users. This can lead to not only severe damage of the CPU, but also your house may burn down. |
. |
. |
Permissions (/dev/i2c-1)#
$ ls -l /dev/i2c-1
$ crw-rw---- 1 root i2c 254, 0 Jun 15 22:52 /dev/i2c-1
/dev/i2c-1 is a character device which permits userspace programs
to communicate with I2C devices that are present on the local
computer. One is able to write a program that opens /dev/i2c-1
just like any other, regular, file. Requests to the device are sent
onto the I2C bus using the write() system call, where responses
are received using read().
Lets say that …
Inside the project that builds the I2C-speaking application, there exists a strong requirement that never, ever a program must run as root. Rather, all programs that belong to the application must run under the credentials of the user
application.Currently, the credentials of user
applicationare$ id application uid=1001(application) gid=1001(application) groups=1001(application)
For a program that will run as user application, and that intends
to communicate with an I2C device, consider the viability of each of
the following options.
Statement |
True |
False |
Why |
|---|---|---|---|
Run the program as root. This will solve the |
. |
. |
|
Btw., running a program as root can have severe consequences. If the program contains malicious (or only buggy) code, it might be able to format your disk, for example. |
. |
. |
|
Ask the administrator of the system that will host the
application to add the user |
. |
. |
|
Convince your manager to spend $$ on Anthropic Mythos 5 to find
another exploitable Linux kernel bug. Embed the generated
exploit into the application to impersonate user
|
. |
. |
Permissions (/etc/passwd)#
/etc/passwd is the user database on Linux/UNIX systems (there are
other kinds of user database on Linux too, but /etc/passwd has
been there since the Epoch).
Lets say the relevant metadata of /etc/passwd are as follows:
$ ls -l /etc/passwd
-rw-r--r--. 1 root root 3401 Apr 22 12:16 /etc/passwd
And the following program is compiled and run as user application
(the same as in Permissions (/dev/i2c-1))
#include <fcntl.h>
int main()
{
int fd = open("/etc/passwd", O_WRONLY|O_TRUNC);
if (fd == -1)
return 1;
else
return 0;
}
Statement |
True |
False |
Why |
|---|---|---|---|
The process will be able to open |
. |
. |
|
The exit code of the program is 1 |
. |
. |
|
The exit code of the program is 0 |
. |
. |
Polymorphic Types (Subtle)#
The following program contains a class hierarchy which exhibits a classic interface/implementation kind of design (though its functionality is rather pointless).
#include <iostream>
class AnyFoo
{
public:
virtual ~AnyFoo() = default;
virtual int do_something() const = 0;
};
class SpecialFoo : public AnyFoo
{
public:
int do_something() const override { return 42; }
};
class BoringFoo : public AnyFoo
{
public:
int do_something() const override { return 7; }
};
int main()
{
AnyFoo foo;
std::cout << foo.do_something() << std::endl;
return 0;
}
Consider the truth values of each of the following statements about that program.
Statement |
True |
False |
Why |
|---|---|---|---|
The output of the program, once compiled, is $ ./program
666
|
. |
. |
|
The program crashes because |
. |
. |
|
The program does not compile because |
. |
. |
|
Your computer will turn in to an expensive brick because
|
. |
. |
Polymorphic Types (Even More Subtle)#
Here is a variation of the program from Stray Pointers which makes use of commandline arguments and varies its execution behaviour depending on an argument that it requires.
#include <iostream>
class AnyFoo
{
public:
virtual ~AnyFoo() = default;
virtual int do_something() const = 0;
};
class SpecialFoo : public AnyFoo
{
public:
int do_something() const override { return 42; }
};
class BoringFoo : public AnyFoo
{
public:
int do_something() const override { return 7; }
};
int main(int argc, char** argv)
{
if (argc != 2) {
std::cerr << "Usage: " << argv[0] << " (special|boring)" << std::endl;
return 1;
}
std::string whichone = argv[1];
SpecialFoo special_foo;
BoringFoo boring_foo;
AnyFoo* foo;
if (whichone == "special")
foo = &special_foo;
else if (whichone == "boring")
foo = &boring_foo;
std::cout << foo->do_something() << std::endl;
return 0;
}
Be program the name of the resulting executable. Consider the
truth values of each of the following statements about that program.
Answer |
True |
False |
Why |
|---|---|---|---|
When called like |
. |
. |
|
When called like |
. |
. |
|
When called like |
. |
. |
|
When called like |
. |
. |
|
When called like |
. |
. |