blog

SPO600 Lab 4 - Building Emacs and Libc

This post will describe how I completed the fourth lab for the SPO600 course. For this lab, I need to choose a package from the GNU project, download it, build it, and test it (without using a package manager). After this, I have to download the GNU Standard C Library (libc), introduce a small change in it, build it, and test it. The second part of this lab (describing the multiarch system) will be in another post.

Just a quick note: I did this lab on my personal computer, an x86_64 machine running Arch Linux.

Before starting, I want to talk quickly about the famous recipe configure + make + make install.

Everyone who used Linux probably used it once or twice. If you have only the source code of a program, but not the compiled binary, you will need to compile it and move the compiled binary to its final location yourself, and that is what the make command does: it is a recipe for how to compile and move the output in your system. This recipe is described in a file called Makefile. When you run the command make, it reads the Makefile, executes the set of instructions described by it to compile your code the right way. After running the make command, you use make install to move the binaries to their final destination. I like to see the make command as a recipe on how to compile and install a program. One thing to notice is: you will not always have a Makefile available; instead, you will have a file called Makefile.in. In order to get a Makefile, we will need to run another command before make: configure.

If the make command is a recipe on how to compile and install something, the configure command is a recipe for a recipe for compiling and installing a program. Why do we need this? Because systems can be very different: they can have different CPU architectures, they may use different compiler versions, different dependencies, different libraries, and so on. By running the configure command, we will actually build a Makefile from our system's settings, based on the Makefile.in.

In short, this is the general idea: we run configure, which is will gather data from our system, and based on it, will build a recipe for how to install the software - it is called Makefile. The make command will then read the Makefile and build the program. We then run the make install command, which also reads the recipe from the Makefile, and will move the program to its final destination.

Package Emacs

I must tell you: I am a VIM person, not because it is a great editor, but because it makes me look cool. However, the cool kids nowadays seem to be using Emacs (in case you don't know what Emacs is, it is a great Operating System, but it has no good text editor), so I decided to install Emacs on my computer.

In the GNU Project website, I found the Emacs package, which took me to a mirror to download the source code. So far so good. I downloaded the tar ball, uncompressed it, and followed the instructions on the INSTALL file.

The INSTALL file recommended me to simply run the configure script. However, I wanted the build in another directory (it is more organized this way), so I switched to another directory and ran the configure script from there.

../emacssrc/configure - I commanded my computer

configure: error: Emacs hasn't been ported to 'x86_64-unknown-linux-gnu' systems. - Screamed the script

Here we go - I mentally tweeted to myself

Turns out, I downloaded the oldest source code for emacs available - one from 2005. That's how I concluded that lists are not always sorted from most to least recent. What a world we live in. I downloaded the newest version and tried again; this time, the script finished successfully. I read the lines that the configure script printed: it listed things such as libraries used, my operating system, architecture, etc. Everything seemed to be right.

I then used the make command to build the application. According to the INSTALL file, it would be built in the src directory. It was interesting to watch the files being created there, while the make command was running. The command finished successfully.

After the built was complete, I would have to run the command make install to move the binary files to their place - since I didn't actually want it installed in my system, I did not do this step.

Now I only had one task left: test the program. I went to the src directory and ran the emacs executable. I have no idea how to use emacs, but it seemed to be working, so I called it a day.

LibC

Now it was time to do the same with glibc. I went to the glibc package page and downloaded the latest source code. Not the one from 1994, but the one from the bottom of the list which is also the latest one (yes, I am a quick learner).

I switched to another directory (so the built files would not get mixed) and ran the following commands:

$ ../glibc-2.26/configure --prefix=/usr

The --prefix option for the configure command will tell the library to use its dependent files from my /usr directory.

And then I ran the make command. It takes a while to finish, so I left it working and went to make a sandwich (this detail is very important).

Now, it is very important to know if my library is working, but even more important, it is good to know if my programs are actually running the library that I just compiled (an alarm is better than something failing silently, right?). So, I like ducks, I think they are great, and I think it would be nice if libC offered support for ducks. So this is what I did: I opened the puts.c (this file contains the definition of the puts function, which prints strings on the screen) file in my text editor and modified it.

Now, here is something interesting: if we use the printf command in a C program, but we only pass one string to it (no other parameters), it will be replaced by the compiler with a call to puts, because it is a lot faster.

Here is the original code:

#include "libioP.h"
#include <string.h>
#include <limits.h>

int
_IO_puts (const char *str)
{
  int result = EOF;
  _IO_size_t len = strlen (str);
  _IO_acquire_lock (_IO_stdout);

  if ((_IO_vtable_offset (_IO_stdout) != 0
       || _IO_fwide (_IO_stdout, -1) == -1)
      && _IO_sputn (_IO_stdout, str, len) == len
      && _IO_putc_unlocked ('\n', _IO_stdout) != EOF)
    result = MIN (INT_MAX, len + 1);

  _IO_release_lock (_IO_stdout);
  return result;
}

weak_alias (_IO_puts, puts)

Here is my modified code:

#include "libioP.h"
#include <string.h>
#include <limits.h>

int
_IO_puts (const char *str)
{
  int result = EOF;
  _IO_acquire_lock (_IO_stdout);

  if ((_IO_vtable_offset (_IO_stdout) != 0
       || _IO_fwide (_IO_stdout, -1) == -1)
      && _IO_sputn (_IO_stdout, "QUACK", 5) == 5
      && _IO_putc_unlocked ('\n', _IO_stdout) != EOF)
    result = MIN (INT_MAX, 5 + 1);

  _IO_release_lock (_IO_stdout);
  return result;
}

weak_alias (_IO_puts, puts)

And here is the result of running a "Hello world" program with my library (the printf is replace with a puts):

PERFECT.