This is an old revision of the document!
Continuing from where we left off in Ex. 1, reading /dev/fb0 may or may not yield the screenshot we expected. If it didn't, the culprit here is most likely the X server, the application that is called upon to draw graphic elements (e.g.: windows) on your screen. When active, Xorg (the X server variant that you're most likely to have installed) places /dev/fb0 under a write lock, meaning that all other processes are prohibited from writing to it. This can be verified with lslock (shows file locks) or lsof (shows opened files).
However, if your script did not generate the desired screenshot but in stead some boot-time messages (related to filesystems and whatnot), then /dev/fb0 is probably available for writes. So what gives? How does the X server render your GUI without writing to the frame buffer? The answer is that it's using a frame buffer, but not that frame buffer. /dev/fb0 is the most primitive frame buffer that's available. That's the frame buffer used by BIOS or UEFI, way before your OS is even loaded in RAM. That's the slow frame buffer. If you have a GPU, then the X server is using its frame buffer, and that frame buffer takes precedence over /dev/fb0.
# check whether Xorg is using the native frame buffer or the GPU frame buffer in /dev/dri $ sudo lsof | grep -e '/dev/fb[0-9]*' -e '/dev/dri/card[0-9]*' | grep Xorg
So then, how do we write the frame buffer? That was the point of the exercise, remember? If the X server is using /dev/fb0, we can't use it since it's locked. If the X server is using /dev/dri/card0, we can use /dev/fb0 but it's pointless. Well, we have a solution for that, but first we need to understand what is a teletype (TTY).
Back in the old days, there were no displays. Not even CRT monitors. When computers started being fast enough to be considered “interactive”, people wanted to have a way to instantly communicate with them. Their solution was to hook said computers up to a teletype writer (see image above), meaning that the terminal output was actually written on paper! In time, the peripherals that we're used to today came to be and the TTY became an abstraction of the old writers (i.e.: unified I/O devices).
As a result, the TTY today can be considered a virtual device that's not hooked to a mechanical keyboard and a roll of paper but might as well be for all we care. Being virtualized, it means that the OS can provide us with as many as we want. And it does. Right now, you are logged in in tty1. The OS provides a basic driver for all TTYs. This driver can echo keys to the screen when you press them, or go to a new line when you hit enter. All the good stuff… But you're too good for that. You wanted a GUI and decided to use the X server. The X server usurped the clean environment that the OS provided and replaced it with colorful pixels. The terminal you're writing in now is not even a real terminal. It's not a TTY. See for yourself:
# print the name of the terminal that's connected to the standard input $ tty /dev/pts/1
That pts there stands for Pseudo-Terminal Slave. A PTS is a userspace program that tries to emulate a TTY, including its driver. On Ubuntu, that program is most likely gnome-terminal. Other examples include Terminator, Alacritty or Kitty. While the terminal emulator tries to recreate the functionality of the original TTY, it relies on Xorg to render each character in it's assigned window, pixel by pixel. And Xorg does this by hiding the true TTY (i.e.: tty1) by directly accessing the frame buffer (either one of them). While Xorg is still alive, we won't be able to accomplish our goal. So is this the solution? Are we killing Xorg? Not necessarily: we can just move to tty2… Xorg is bound to tty1 by default, but the OS can make up as many TTYs as we want. Let's do that:
# switch the system to tty2 (also works with <Ctrl + Alt + F2>) $ sudo chvt 2 # tty2 doesn't know about GPUs; it uses /dev/fb0 # Xorg is sleeping since we're not using tty1 $ sudo dd if=/dev/urandom of=/dev/fb0 # chaos ensues # clean up with <Ctrl + L> or $ clear # return to a more familiar sight (<Ctrl + Alt + F1>) $ sudo chvt 1
You're most likely using a much newer version than 17.10, but if you want to check:
$ cat /etc/lsb-release
The reason why we're discussing only tty1-7 is that they are mapped to your Function keys (F1-F7) and can be accessed by hitting <Ctrl + Alt + Fx>. Your system has a lot more TTYs available:
# yes, all virtual TTYs have an entry in /dev ls -l /dev/tty[0-9]*
We've just confirmed that while in tty2, we can safely (and successfully) write to /dev/fb0. Moreover, once we switch back to tty1, if Xorg uses the GPU and bypasses the native framebuffer, /dev/fb0 will contain the last image rendered while in tty2. If you're running Linux in a VM this is unlikely, but not an issue. So here's the task:
This new script will have to do the exact opposite of fb2img.py. The script will take an image of your choosing and write it to the frame buffer (you know which one). Also, it must take the following arguments:
positional arguments: FILE input image file options: -h, --help show this help message and exit --dst /dev/fb* data destination --width INT screen width [px] --height INT screen height [px] --hoff INT horizontal offset [px] --voff INT vertical offset [px]
The result should look something like this:
# create a copy of /dev/fb0 in persistent storage & change ownership to yourself $ sudo cp /dev/fb0 frame_buffer.bin $ sudo chown student !$ # create a backup with the .bak extension $ cp frame_buffer.bin{,.bak} # test img2fb.py with the persistent copy as destination $ ./img2fb.py --dst frame_buffer.bin nyan_cat.jpg # extract an actual image from the modified copy of the framebuffer $ ./fb2img.py --src frame_buffer.bin does_this_look_good.png # you screwed up: revert changes and try again $ cp frame_buffer.bin{.bak,}