Friday, February 11, 2011

Wide-character API of glibc

I've had to write an application that deals with text in virtually any language, at least, any latin-based language. The source of the text is in files that are encoded in UTF-8, so the question was "how do I deal with this new type of text data?".


Glibc (and others) provide the wide char (wchar_t) data type, which is able to represent any character by using a fixed-width representation of a character, in my platform, 32 bits wide.


There are functions used to manipulate text data in wide char format, and most of them are equivalent to the classic 8-bit string functions: fwprintf, swprintf, wcslen, fputwc, fgetwc, etc...


However, there some tricks that must be taken into account. There is a new operator "L" used to generate wide-char text constants, so the statement:


char   mystring[] = "hello";


in the wide-char form must be expresed as:


wchar_t  mystring[] = L"hello";


Note that the "format" argument used in all wprintf functions is also a wide-char text, so it needs to be converted to wide-char text using the "L" operator as well.


Once the data is in wchar format, all operations are similar to the classic equivalents, but now the point is how to convert to/from the classic string format. Well, to be able to convert to a wchar character which has more bits than the standard 8-bit representation, some kind of encoding must be used. For example, UTF-8 or UTF-16 are methods to represent wide chars using 8-bit octets. It is important to note that the type of encoding used is independent of the code or the functions used in the code. All the code needs are a couple of functions to convert to/from byte sequences from/to wide chars. The particular type of encoding is not considered here and depends on the operating system's resources and their configuration, usually in the internationalization settings.

For example, the mbsrtowcs and wcsrtombs functions are used to convert from multibyte sequences to wide char strings, and from wide char strings to multibyte sequences, respectively. 

The setlocale function must be used to select a particular internationalizaton setting used to convert between wide-char and multibyte sequence strings, for example:


setlocale(LC_CTYPE, "en_US.UTF-8");


This setting is used by glibc to do the conversion in the mbs/wc functions. Note that the proper conversion files are required. In Linux, these are under the /usr/lib/locale directory, organized by locale name. In the example above, the files searched would be, in precedence order:


/usr/lib/locale/en_US.UTF-8/LC_CTYPE
/usr/lib/locale/en_US.utf8/LC_CTYPE
/usr/lib/locale/en_US/LC_CTYPE


The file must be generated with the 'localedef' utility, provded by the glibc installation:


localedef -i en_US -f UTF-8 /tmp


generates the internationalization files for the locale in the exmaple under the /tmp directory. Embedded system developers should note that the LC_CTYPE file is 256 KB big!, which is too much for some systems. The problem is that, without these files, the mbs/wc and fput/fget functions simply don't work, indicating that "an invalid byte sequence cannot be converted".


In this case, one has to write his own conversion functions at the price of losing some portability of the code. For example, it is cheaper in terms of disk space, to write our own UTF-8 to/from wide-char conversion functions, but we are limited to this type of encoding.




Generation of simple PDF files

The goal is to produce a very simple PDF file that contains unformatted text without using very heavy resources, such as PDF converters, graphics libraries, etc, and match the limited resources available on an embedded platform.

PDF generation in this simple way is quite straightforward. The Adobe's PDF specification provides a couple of examples of simple PDF files that can be used as a template for a very simple PDF rendering library.

Basically, a PDF file is a collection of objects linked from a global object table (xref) that lists the offsets where each object can be located in the file. Some objects have references to other objects as well.


Problems come when the text to be converted to PDF contains special characters, such as those used in the eastern european ones. The PDF specification states that all characters in the text must have 8 bits only, and PDF doesn't know anything about UTF nor Unicode encodings. For each 8-bit character in the text, a reader will fetch the corresponding glyph from the font file selected in the font-type PDF object. The fetch is done using the 8-bit value of the character and taking into account the encoding of the font. Only two types of font encodings are supported : WinAnsiEncoding and MacExpertEncoding, which are used in Windows and MAC, respectively. This encoding is defined in a "Font"  PDF object, or in an "Encoding" object referenced by a "Font" object. However, the windows font encoding is always fixed to the CP-1252 code table. Unfortunately, this encoding only covers some non-ASCII characters, but most of them are not. For example, most of the chars used in eastern european languages (polish, hungarian, etc) are not defined in CP-1252, but in CP-1250 code page.

With these limitations, it is not possible to use non-CP1252 chars in a text stream of a PDF file. There is, however, a workaround that may work in some cases. PDF provides the so-called "font encoding differences", which is an optional entry of the font encoding dictionary that allows the writer to map a given set of character codes in the text to a set of glyphs in the selected font. Glyphs are defined by a standard name, and a list of official glyph names can be retrieved from the Adobe Glyph name list. However, not all glyphs are available in all fonts, of course.


This is an example of this mapping:

10 0 obj
<
/Subtype /Type1
/Name /F1
/BaseFont /MyriadPro
/Encoding 11 0 R
>>
endobj
11 0 obj
<
/BaseEncoding /WinAnsiEncoding
/Differences [ 156 /sacute 230 /cacute
241 /nacute 179 /lslash
]
>>
endobj


In this example, there are two objects: a Font object (10) and an Encoding object (11). The font object uses the Encoding object to define the encoding of the font, which in turn includes the encoding differences. In this example, char code 156 is mapped to the "ś" character, code 230 is mapped to "ć", code 241 is mapped to "ń" and code 179 is mapped to "ł", which are some of the characters of the polish alphabet, and the codes are taken from the CP-1250 code table, which is NOT supported by PDF.

Note that the font type used (MyriadPro in this case) must contain the glyphs used in the Encoding object: sacute, cacute, nacute and lslash. Otherwise, the .notdef glyph would be displayed by the reader. For exmaple, the basic "Courier", "Arial" and "Helvetica" fonts I tried, did NOT contain these glyphs. In particular, I couldn't find any standard proportional font containing these glyphs.


So, for this method to work, it requires that the operating system where the PDF reader is running, provides the font defined in the PDF file, which is a non-standard font. A better solution would be embedding these fonts in the PDF file, but this is not a easy task. 
Unfortunately, this seems to be only safe solution, that is guaranteed to work in any system or environment.


The next step is to understand and learn how to embed font programs in PDF files, in an easy way.

USB File Storage Gadget

Today I have enabled the USB gadget support for file storage. The intention is to be able to export files via the USB device interface to a PC.

The file storage gadget must be enabled at the kernel config menu:
USB support -> Support for USB gadgets -> File-backed storage gadget

Note that only one USB gadget may be enabled at the same time. If multiple gadgets must be supported, all of them must be configured as modules, so I had to remove built-in support for ethernet gadget from the kernel. Switching the USB function requires removing and installing the proper modules.

The module for file storage is g_file_storage.o and is installed this way:
insmod g_file_storage.o file=/results.bin stall=0

The 'stall' argument is necessary for the USB disk to be properly detected by windows. Linux does not require this argument and the drive can be mounted without problem. If 'stall' is not set to zero and the gadget is connected to a windows PC, the following messages appear:
g_file_storage pxa2xx_udc: full speed config #1
udc: pxa2xx_ep_disable, ep1in-bulk not enabled
udc: pxa2xx_ep_disable, ep2out-bulk not enabled
udc: USB reset
udc: USB reset

repeating every few seconds.

For the 'stall' option to be available, it is necessary to enable the 'file-backed storage gadget in test mode' option in the kernel configuration.

Multiple files may be specified when the gadget module is installed, thus creating multiple drives visible to the remote host.


Any volume size may be created but it seems that Windows assigns floppy drive letters if the volume size is similar to a floppy device size. I have tested 720KB and 1440KB only.

The volume may be declared as read-only by using the "ro=1" parameter at the insmod.

The backend file may be either a disk partition or a file image.
An initial filesystem image can be created this way:

# dd if=/dev/zero of=results.bin bs=512 count=2880
# mkdosfs results.bin

Then, loop-mount the image file and populate the filsystem. Here is where problems came: if a process writes a new file to the loop filesystem, the host side of the USB connection (where the file browser runs) does not see the new file, even if the file browser is refreshed. The only workaround is to unplug/plug again the USB cable. This happens even if a 'sync' command is run on the tester device.



Also, some inconsistences happen if the USB host side writes to the device. The device doesn't see the new files, and vice-versa.


In conclusion, it is quite a good method to export only file from a Linux device, but with some limitations on "live" filesystems.




Wednesday, February 3, 2010

Multiple gateways on the same host

Having two gateways on the same host, where some processes send outgoing traffic over one gateway while the rest use the other gateway, requires a virtual network interface to be set up, and have a separate routing table so that all traffic to/from this virtual interface uses the secondary
routing table where the other gateway is in the default route.

First, I'll describe the steps one by one and later I'll explain how to make this setup persistent so that the system boots correctly the next time. Let's assume we have two gateways:

gw1 : 172.26.2.100
gw2 : 172.26.3.100

Create a virtual interface which will be used by processes that need to send traffic to gateway gw2:


$ ifconfig eth0:1 172.26.3.209

Create a definition and give a name to the new routing table (index 1, name 'test'):
$ echo "1 test" >> /etc/iproute2/rt_tables

Show the main routing table:

$ ip route show table main
172.26.0.0/16 dev eth0 proto kernel scope link src 172.26.3.206
169.254.0.0/16 dev eth0 scope link metric 1002
default via 172.26.2.100 dev eth0

Clear the secondary routing table:

$ ip route flush table test

Copy all rules from main table to secondary table, but the default gateway

$ ip route show table main | egrep -Ev "^default" | while read route; do
ip route add table test $route
done

Add the gateway for the secondary routing table:

$ ip route add table test default via 172.26.3.100

List the secondary routing table:

$ ip route show table test
172.26.0.0/16 dev eth0 proto kernel scope link src 172.26.3.206
169.254.0.0/16 dev eth0 scope link metric 1002
default via 172.26.3.100 dev eth0

Add a rule so that for any packet to/from the virtual interface, the secondary routing table is applied:

$ ip rule add from 172.267.3.209 lookup test
$ ip rule add to 172.267.3.209 lookup test

At this point, traffic originated from interface eth0:1 will use gateway gw2 (172.26.3.100) and traffic from interface eth0 enroutes via the default gateway gw1 (172.26.2.100). Try and compare:

$ traceroute -s 172.26.3.206 www.google.com
$ traceroute -s 172.26.3.209 www.google.com

In order to make the changes above persistent, edit/create the following files:

1. Add a description for the 'test' routing table (already done):

$ echo "1 test" >> /etc/iproute2/rt_tables

2. Create file '/etc/sysconfig/network-scripts/ifcfg-eth0:1' containing the
configuration of the virtual interface:

DEVICE=eth0:1
ONBOOT=yes
SEARCH="mydomain.biz"
DOMAIN="mydomain.biz"
DNS1=172.26.2.200
DNS2=172.26.2.201
BOOTPROTO=none
NETMASK=255.255.0.0
IPADDR=172.26.3.209
TYPE=Ethernet
USERCTL=no
PEERDNS=yes
IPV6INIT=no
NM_CONTROLLED=no

3. Create file '/etc/sysconfig/network-scripts/route-eth0:1' containing the 'test' routing table:

table test 172.26.0.0/16 dev eth0 proto kernel scope link src 172.26.3.206
table test 169.254.0.0/16 dev eth0 scope link metric 1002
table test default via 172.26.3.100

4. Create file '/etc/sysconfig/network-scripts/rule-eth0:1' containing the rules for the virtual interface:

from 172.26.3.209 lookup test
to 172.26.3.209 lookup test

The above explanations have been tested on a Fedora 10 distribution.

Friday, January 15, 2010

Extracting the contents of a ramdisk image

Sometimes it is necessary to examine the contents of a ramdisk image (initrd). An initrd image is basically a gzip-compressed cpio archive.

Here are the steps used to extract the files contained in a ramdisk:

gunzip initrd
mkdir tmp
cd tmp
cpio -i

Similarly, to build an initial ramdisk image from a directory:

cd tmp
find . | cpio -o -H newc | gzip -9 ../initrd.img

Where 'newc' is the name of the format used in the cpio archive.

In a Fedora distribution, the ramdisk init script is a nash shell-script. Nash is a very reduced footprint shell with built-in commands targetted to ramdisk operations such as device node creation, module loading, root device creation, root pivot, etc.

One of the most important nash commands is 'mkrootdev', which creates the root device specified as an argument. After this command is run, the "mount /sysroot" command is executed, which mounts the root filesystem on the root device.

If the root device is not correctly specified here, the following error will show up:

"mount: error mounting /dev/root on /sysroot as ext3: No such file or
directory"

mkinitrd is the responsible for creating the init script contained in a initrd image, and the name of the root device is obtained from the /etc/fstab file, when the kernel rpm is installed (not when it is built!). So, it is important to have a correct fstab file in the filesystem before the kernel is installed, otherwise mkinitrd won't be able to figure out the root device and set the correct arguments to the mkrootdev command.


Thursday, January 14, 2010

Add a new config patch to a Linux kernel RPM

Sometimes it is necessary to patch a kernel and rebuild the original rpm so that the installation of the new kernel and its modules is easier. I will not explain here how to patch a kernel, so I assume that we already have a patch available that has been tested and applies correctly.

We must have the source rpm of the kernel we intend to patch and rebuild. The following explanation has been tested on a kernel rpm from a Fedora 10 distro and, in particular, I will focus on patching the kernel config files rather than patching the source files.

First of all, an explanation of how kernel configuration is achieved by the spec file of a Fedora kernel rpm:

1. Some extra config files are provided along with the kernel source tarball. These files are named 'config-*' and are declared in the spec file using the 'sourceNN:' directives. The config files are hierchical, that is, the definitions add hierchichally one on top the other. For example, the configuration file for the i686 architecture is produced by adding the config-generic, config-x86-generic and config-i686 config files. The merge.pl perl script, also provided as an extra source file to the rpm, is responsible for doing the merge of the config files.

2. The config-* files are copied into the buildroot directory of the RPM, that is under BUILD/kernel- directory.

3. A 'make configs' rule is executed so that the config-* files are merged and produce a set of kernel-*.config files also in the build root directory. All configs are generated, even if they are not intended for the architecture that is going to be built.

4. At this point, all patches are applied using the 'ApplyPatch' macro in the spec file. Note that any patch that changes the kernel configuration must change the kernel*.config files, Patching the config-* files would not work as these are not taken into account for the build process after the patches are applied. Similarly, patching the .config file is not an option since this file will be overwritten by each kernel-*.config file during the build process.

5. The config files not intended for the architecture that is being built are deleted.

6. 'make oldconfig' is run on all the remaining kernel-*.config files, and the resulting config file is saved in the configs/ directory and removed from the root build directory. This is why the kernel-*.config files are not present even if we only run the patch stage of rpmbuild (-bp).

So, to have a patch that changes kernel config we have two options:

A. Edit the SOURCE/config-* files provided by the kernel source RPM and rebuild the source rpm with the new ones.

B. Generate a patch that modifies the kernel*.config files.

There some pros and cons to each option. Option B is more modular, so in case we choose to remove some kernel configuration in the future, all we have to do is remove the patch from the spec file, whereas if we use option A, the original source file is modified and it makes more difficult to revert some config changes.

On the contrary, option A is easier than option B because the kernel*.config files that must be patched are autogenerated during the rpm build process and are not available before then.

In order to apply option B, we should have a pristine kernel build root containing the patched kernel and the kernel*.config files so that these can be patched. Then, make a copy of the entire root and edit the kernel config files for the intended architectures, generate a patch by diff'ing both directories, copy it into the SOURCE directory and declare it in the SPEC file. Here is an example:

1. Generate a pristine build root of the linux kernel rpm

$ rpm -i kernel-2.6.27.5.src.rpm
$ cd ~/rpmbuild

Edit the SPEC file, by adding an 'exit 1' sentence after the last patch is applied (a call to the 'ApplyPatch' macro). This causes the patch process to be interrupted after the kernel*.config files are merged but before they are oldconfig'ed and moved to the configs/ directory. Perhaps there is a more elegant way to do this, but I don't know of it.

Extract the sources and apply the patches:

$ rpmbuild --target=i686 -bp SPECS/kernel.spec

Note the -bp option tells rpmbuild to stop after the source tarball is extracted and all patches are applied. Because we added the 'exit 1' statement, rpmbuild will return an error which of course can be ignored.

2. Make the changes in the kernel config files

$ cd BUILD/kernel-2.6.27.5
$ cp -a linux-2.6.27.5.i686 linux-2.6.27.5.i686.new
$ cd linux-2.6.27.5.i686.new

Edit each kernel-*.config files that applies to the intended architectures.

Changing the config files may be a bit painful, depending on the dependencies of the config items that we change. To make sure that the changes are ok, at the end we must run 'make nonint_oldconfig' with does some sort of check on our config file. Another option is to copy each kernel*.config file as .config and configure the kernel manually using 'make menuconfig', and then copying it back to the original file.

3. Generate the patch

$ cd ..
$ diff -urN linux-2.6.27.5.i686/ linux-2.6.27.5.i686.new/ > newpatch

Edit newpatch and make sure that only the changes to the kernel-*.config files are included. Then, rename and move the new patch to the SOURCES directory:

$ mv newpatch ../../SOURCES/linux-2.6.27.5-newconfig.patch

And declare the new patch in the SPEC file, by adding this line after the last call to the ApplyPatch macro:

ApplyPatch linux-2.6.27.5-newconfig.patch

# END OF PATCH APPLICATIONS

And, of course, remove the 'exit 1' statement.

4. Rebuild the RPM

As usual, run:

$ rpmbuild -ba --target=i686 SPECS/kernel.spec

After these steps, both a binary RPM and a source RPM will be present under the RPMS/i686 and SRPMS directories, respectively.

Of course, if we already have a patch for the kernel, all we have do is copy it to the SOURCES directory with a proper name, add it to the spec file by using the ApplyPatch macro, and rebuild the rpm (rpmbuild -ba).

Note the Fedora kernel spec file builds several flavours of the same kernel architecture, for instance: with or without PAE, debug, SMP, and several combinations of these. So many combinations may take a lot of time to build the rpms. If we are interested in only the base kernel without PAE, debug, etc, all we have to do is add the "--with baseonly" option to the rpmbuild command. Other variants are allowed, --with / --without pae, debug, etc....



Monday, July 6, 2009

Some problems with GPS and NTPD

I found some problems when trying to set up the ntpd server connected to a GPS.

First, I had to solve how to create the devices required by ntpd: /dev/gps0 and /dev/gpspps0. This is easy: just create a udev rules file, for example /etc/udev/rules.d/90-gps.rules:

SUBSYSTEM=="pps", MODE="0660" GROUP="uucp" SYMLINK="gps%k"
KERNEL=="ttyS0", SYMLINK="gps0"

the above creates the following links:
gps0 -> ttyS0
gpspps0 -> pps0

and the target devices are accessible to members of the uucp group. Therefore, the 'ntp' user must belong to the uucp group:

# usermod -G ntp,uucp ntp

Then ntpd experienced some serious jitter problems in the system clock, and I found out that the ntpd driver was not using the PPS signal, but only the NMEA output. Running ntpd in debug mode (-d flag) showed a 'permission denied' error when opening /dev/gpspps0. The file permissions are ok, but the problem was in the selinux layer. I inadvertently had the selinux enabled. Disabling it fixed the problem (set to 'disable' in /etc/selinux/config).

Also, the serial port had to be carefully set up before ntpd was started, otherwise, some interactions in the tty driver would be executed, and the DCD interrupt detection would be lost, as explained in the previous posting. The minimum settings of the serial port at start-up are:

# stty -F /dev/gps0 raw ispeed 4800 ospeed 4800 -hupcl

In particular, finding the selinux problem was quite confusing sometimes. If ntpd is started from the command-line as root, the "permission denied" error does not show up. However, if started using the 'service ntpd start' command, the error appears.

Wednesday, July 1, 2009

Synchronizing an NTP server to a GPS/PPS

The goal now is to have a Garmin GPS 18LVC driving a PPS (pulse-per-second) signal to the ntpd server for a highly accurate time reference server.

1. The GPS device

There are at least a couple of ways to propagate the PPS signal to the ntpd server, plus some variants in each case. However, the GPS device must be seen as a device that sources two different types of data: the absolute date and time, and the 1-Hz clock signal (PPS).

The first one provides the complete information about when it is now, including a complete time and date, but with poor accuracy because this information is sent over the data lines of the serial port and encoded using some type of protocol, i.e. NMEA. PPS provides a very accurate clock
(about 1 uS in the GPS 18LVC device) but without any reference to the absolute time. This clock is wired to the DCD (Data Carrier Detect) pin of the serial port. In other words, PPS tells us with good precision when each second begins, but it doesn't tell us which second it is. This timing
information must be combined with the protocol messages sent by the GPS to have both precision and a complete timestamp at the same time.

The Garmin 18LVC model speaks the NMEA protocol, which sends out every second the NMEA messages that have been previously selected with the PGRMO command. Only the GPRMC message is necessary to get the current date and time, and it is important to keep to amount of information sent out over the serial port every second to a minimum. Once the PGRMO command has selected the message types, the GPS will retain this configuration in its internal memory.

The output from the GPS can be seen using a terminal program, such as minicom, if properly configured at 4800 baud, no parity, 1 stop bit.

2. NTPD reference clocks


The ntpd server supports several types of drivers. Here, the term 'driver' has nothing to do with a 'kernel driver'. ntps drivers are low-level callback functions that are registered within the ntpd core and implement the access to several types of local clocks, such as GPSs , radio clocks, etc. Each driver is identified by a pseudo-IP address identifier. The identifiers involved here are:

127.127.20.x : NMEA Reference Clock driver
127.127.28.x : SHM (shared memory) driver

2.1 NMEA reference clock

The NMEA clock driver assumes that a GPS device sending out NMEA messages is connected to the system via a serial port, named /dev/gpsX and its PPS signal, wired through the DCD pin, is accessible from a /dev/gpsppsX device.

/dev/gpsX is actually a link to some /dev/ttySX serial device, and /dev/gpsppsX is a link to a /dev/ppsX device which, in turn, is provided by the kernel PPS API. This API collects and distributes a precision kernel clock information from/to userland programs, and supports some predefined client drivers, such as the DCD pin connected to a 8250 UART. The DCD pin is sensed using a new serial line discipline, named PPS, which is an extension of the TTY line discipline. The sensing takes place during interrupt time, so it provides a very precise timestamping of the DCD events.

This PPS API, also known as LinuxPPS, is not yet available in the kernel, so a patch must be applied to the kernel mainline tree.

Note the /dev/gpsppsX device is optional and if ntpd cannot open this device at startup, it will silently fall back to the NMEA-only functionality. This means that it will use the arrival time of the NMEA messages to discipline the system clock, which will result in a very poor precision, usually, worse than using a remote NTP server over the internet. Running ntpd in debug mode will, however, log this condition.

The /etc/ntp.conf must contain these lines:


server 127.127.20.0 mode 1 minpoll 4 prefer
fudge 127.127.20.0 flag3 1 flag2 0 time1 0.0

Here note that a single NTP device provides both the timestamp and the PPS timing. The meaning of these flags are as follows:

mode=1, means that only the GPRMC messages of the NMEA protocol will be analyzed. flag3=1 tells ntpd to use the PPS line discipline of the kernel, and flag2=0 tells the driver to use the rising edge of the DCD signal to signal the start of each second.

In order to activate the PPS line discipline on the serial port connected to the GPS, it is necessary to run the 'ldattach' utility, which actually will stay in the background to keep the serial port open and the discipline active:

# ldattach pps /dev/ttyS0


2.2 SHM reference clock

The SHM driver accepts delayed timing information from a System-V IPC shared memory (with key "NTPx"). The timing information is written there by some external process, whatever it is. This process would read the timing information from the GPS and write it to the shared memory so that ntpd can process it. There are some user-space utilities that can do this job, for example, gpsd and shmpps. Gpsd is a general-purpose daemon that has been designed to talk to most types of GPS models using a wide variety of protocols and, in addition, is capable of processing the PPS signals and sending timing information to ntpd via a shared memory device. Or, at least,
this is what it claims to do... because I couldn't achieve this.

2.2.1 gpsd

Actually gpds feeds two devices to ntpd, one with the absolute timestamp parsed from the NMEA messages, or any other protocol supported by gpsd, and another feeding the PPS timing information. Ntpd sees both devices as two different SHM devices, so the ntp.conf file must be like this:

server 127.127.28.0 minpoll 4
fudge 127.127.28.0 refid GPS
server 127.127.28.1 minpoll 4 prefer
fudge 127.127.28.1 refid PPS

2.2.2. shmpps

shmpps is a much simpler daemon that does exactly one job: detect the PPS changes, get a timestamp for each change, and send them to ntpd via a single shared memory. No absolute time is passed to ntpd, so ntpd should still use a NMEA device (on the same serial port) to get the absolute time reference.

So, /etc/ntp.conf must look like this:

server 127.127.20.0 mode 1 minpoll 4 prefer
fudge 127.127.20.0 flag3 0 flag2 0 refid NMEA
server 127.127.28.0 minpoll 4
fudge 127.127.28.0 refid PPS

Both gpsd and shmpps share a common problem: they detect DCD changes from user-space, that is, a program that blocks on the TIOCMGET ioctl until a change is detected and then gets a timestamp. In this case, the latency is larger that in the PPS kernel API, where the timestamp is read at interrupt time.

In conclusion, the LinuxPPS approach offers the best precision and simplicity as it requires no external daemons, but the kernel must be patched. On the other side, the SHM devices offer worse precision but are easier to set up.

3. How to enable support for PPS API

Here I explain how to patch and build the software modules involved in having GPS/PPS connected to ntpd. I also rebuilt the RPMs for these modules so that instalations is easier. The process involved in rebuilding the RPMs is also described below.

3.1. Building the Linux kernel

There are two ways to get LinuxPPS: via git or via patches. The latest version of LinuxPPS is available only via the git repository and it contains an entire Linux kernel tree, but only for one
kernel version version (2.6.28-rc6 at the time of writing). However, the patches are against a wider repertory of kernel versions, but the LinuxPPS implementations are older.

I'd rather not use the kernel available from the git repository because I prefer to stick to the same kernel version that is already installed in the target system, in my case, a Fedora 10 (2.6.27.5-117) but, at the same time, I want to have the latest LinuxPPS implementation.

So, I decided to patch the 2.6.27 kernel manually. This shouldn't take much time, as the patches look quite straightforward to apply.

First, I downloaded the entire git repository and diff'ed it against a stock 2.6.28-rc6 kernel, the resulting patch is the LinuxPPS implementation.

To get the original kernel that comes with my Fedora 10, I had to download the source RPM for the installed kernel version (2.6.27.5-117) and install it. This must be done on the target PC.

Then, I prepared a source tree for the patch and build:

# cd ~/rpmbuild
# rpmbuild --target i686 -bp SPECS/kernel.spec

Note the spec file already creates the kernel config file so there is no need to configure the kernel to match our current settings.

Just to test, I tried to apply the LinuxPPs patch directly to my 2.6.27.5 kernel but, of course, it didn't work as there were too many differences.

Then, I split the patch into two parts: one containing the new files specific to the pps implementation, and another for the existing kernel files. The first sub-patch was applied successfully, and the second one had to be manually applied, but it wasn't too painful. At the end,
I diff'ed against the original 2.6.27.5 kernel and saved them to a patch file (patch-2.6.27.5-ppsapi).

Then, I configured the kernel to enable PPS support:

make menuconfig
Device drivers -> PPS support
* Enable high-resolution timestamps
* Enable 'ktimer' and 'line discipline' as modules

General setup -> append to kernel version: "pps" to have this tag
on the kernel version string.

Then I compiled the kernel and the modules

# make
# make install

Check that /etc/grub.conf has the correct entries (it should have) so that when the system boots again, the new kernel is used

Now, the kernel header files must be prepared for compilation of the user-space tools:

# cd /usr/include
# mv linux linux.orig
# mv asm asm.orig
# mv asm-generic asm-generic.orig
# ln -s /lib/modules/2.6.27.5pps/build/include/linux
# ln -s /lib/modules/2.6.27.5pps/build/include/asm
# ln -s /lib/modules/2.6.27.5pps/build/include/asm-generic
# cp /lib/modules/2.6.27.5pps/build/Documentation/pps/timepps.h .

Note the timepps.h header is required for ntpd to detect the presence of the PPSAPI in the Linux kernel. Otherwise, ntpd wouldn't complain and silently revert to using the NMEA protocol only.

3.2. Building the 'ntpd' server


Now, it is time to patch and compile the NTP server (ntpd). I used the latest available release, 4.2.4p7 at the time of writing.

Initially, I used the nmea.patch from the LinuxPPS homepage but the resulting ntpd failed to detect the PPS signal from the GPS. After some detailed debugging using gdb, I found out that the DCD interrupts from the UART were disabled by ntpd during initialization, preventing the PPS signal from reaching the processor.

A more detailed debugging showed that the call to tcsetattr() in the refclock_setup function, indirectly caused the DCD interrupts to be disabled, though the bit mask of c_iflag should not cause this problem. I believe that changing some of the c_iflag bits causes the UART to
be incorrectly reprogrammed, perhaps this is why the ldattach utility requires patching. So, I decided to patch ldattach so that the c_iflag settings are the same as the ones set by ntpd. This is a dirty hack, but it seems to be how the LinuxPPS patches work here.

If you are experiencing problems and you suspect that PPS is not being read by ntpd, try reading the IER register (Interrupt Enable Register) of your 8250 UART (or compatible) and check that bit 3 is set. The IER register is at offset 0x01 of the UART (i.e. address 0x3F9).

To compile ntpd, download ntp-4.2.4p7 and apply the patch-ntp-4.2.4p7-ppsapi, then configure it to enable the NMEA driver (ID 127.127.20.u):

# ./configure --disable-all-clocks --disable-parse-clocks --enable-NMEA --enable-linuxcaps

Additionally, to may want to enable support for SHM drivers, in case you want to experience with user-space drivers, which don't require kernel patching but are more likely to be affected by latencies and be less precise. If so, add the "--enable-SHM" argument to the configure command.

Now, run 'make' and use the produced 'ntpd' driver and utilities.

Remember that the Linux PPSAPI must be enabled in the kernel, and that the correct kernel include files must be visible under /usr/include, as explained in the previous chapter.

3.3. Building the ldattach utility

The ldattach utility is used to set the line discipline associated to a serial port. LinuxPPS uses a new line discipline, named PPS, that detects changes in the DCD line of the serial port and feeds those changes into the kernel PPS API.

A small patch must be applied to ldattach, but it must be slightly different to the one proposed in the LinuxPPS homepage. I believe the spirit behind this patch is to set the same terminal config both by ldattach and ntpd so that no change is done and the UART registers preserve the
DCD detection.

ldattach is provided by the util-linux package in the Fedora 10 distribution.

The easiest way is to install the source RPM, add the new patch in the spec file and rebuild the rpm.

3.4 Rebuilding the RPMs

The affected RPMs are: kernel, kernel-headers, ntp, ntpdate, util-linux-ng and their debug and devel variants.

Rebuilding an RPM so that more patches are applied is quite straighforward. Usually, an RPM that comes from a distribution package has already several patches that get applied when the rpm is built. All we have to do is add the corresponding pps patches to each spec file and rebuild the RPM.

In general, the process is as follows: the source RPM is installed and this installs a spec file in the SPECS directory, and a source tarball and the patch files under the SOURCES directory. The SPECS and SOURCES directory can be found under the buildroot directory of the rpmbuild utility, usually under ~/rpmbuild. Then, the additional pps patch must be copied in the SOURCES
directory, and then edit the spec file to add the new patch file using a %patch clause. Number the patch clause so that our patch is applied after the others.

Also, it is a good idea to add the 'pps' suffix in the release string of the RPM, otherwise it would be impossible to tell a pps-capable rpm from a non-capable one.

The kernel RPM is the most complex of these RPMs, and the spec file had to be changed more:

* The 'buildid' macro was defined to be ".pps"
* Patches are applied using the "ApplyPatch" macro
* The timepps.h header must be copied to /usr/include when building
the kernel-headers rpm.

As described in the above paragraphs, before building the util-linux and ntp packages, the links to the new kernel include files must be done. Alternatively, the pps kernel rpm can be built and installed first, and then rebuild the rest of the packages.

Monday, June 15, 2009

Installing Fedora from a USB pendrive

I had to install Fedora 10 on a PC without floppy disk and DVD/CD-ROM. The only choice is to boot from USB-HD which is supported by the BIOS. Here is the process, step by step:

1. Install the livecd tools on a Linux PC:

# yum install livcecd-tools

2. Download the DVD iso image of the Fedora distribution that must be installed on the target system. Pay attention to chooses the correct architecture (i686 or x86_64).

3. Loop-mount the ISO image to a temporary directory:

# mkdir /mnt/tmp
# mount -o loop /mnt/tmp

4. Insert the USB drive and make sure it is not mounted. Then , run the script that creates a live (bootable) CD:

# livecd-iso-to-disk --reset-mbr /mnt/tmp/images/boot.iso /dev/sdb1

5. Mount the USB drive and copy the installer and the ISO image to the USB drive:

# mkdir /media/usbdisk/images
# cp /mnt/tmp/images/install.img /media/usbdisk/images
# cp Fedora-10-i386.iso /media/usbdisk/

6. Unmount the USB drive, and insert it on the target system. The Anaconda installer must show up shortly after power-up. Select Install Media from "Hard drive" and proceed as in a normal DVD installation.

More problems with OWAMP behind NAT

Well, not all NAT problems were solved with the fixes detailed in the previous post. Indeed, the server does not check that the OWAMP client is sending from the address specified in the "receiver IP address" field of the Request-session message, but there are some outstanding problems.

First, the server will create a new socket for the test session, and will attempt to bind that socket to the address specified for the sender or the receiver in the request-session message. This address is, of course, the public address of the server as the message was created and sent by the client. Obviously, the server will fail in binding that socket to a public address it is not in none of its interfaces.

Second, the client must specify its actual public address in the sender/receiver IP address field of the request-session message, otherwise, the other end will not be able to send test packets to that address.

Therefore, some modification is required in the OWAMP code. On the client side, the public address must be specified in the messages. Also, on the server side, the server should bind the test socket to the private address, regardless of the address specified in the session-request message.

The client needs to know its actual public address on the public side of the NAT, which is specified by the new '-x' option of the owping program. The owampd daemon only needs to know if it is running behind a NAT or not, but it doesn't know the public address, makeing the server setup more simple. The -N flag is used in this case.

So, the extra requirement is for the client to know its public address, which can be obtained by a STUN client or a similar service.

Now, it definitely works fine, with both the server and the client running behind their respective NATs.

Friday, June 5, 2009

OWAMP problems behind NAT

I've been doing some tests with the OWAMP protocol (One-way Active Measurement Protocol) and it turns out that it is not possible to ow-ping a server when the owamp client (owping) is behind a NAT. The client returns a 'server denied access' error.

OWAMP is one of those non-NAT-traversable protocols, such as SIP, as it passes endpoint IP addresses in the protocol messages. If the client is sitting behind a NAT, the source address passed is not the same as the actual source IP address as seen by the server. During a test session request stage, the owamp server checks that both addresses are the same, in order to prevent attacks, si one would think that the OWAMP protocol is unusable if the client is behind a NAT.

But not. It happens that the owamp server only checks the addresses in open mode. So, if we enable the authenticated mode, for example, the check is omitted and everything works.

To work in authenticated mode, all you need to do is to setup a common passphrase in both sides so that the client gets authenticated. The passphrases are kept in the owampd.pfs file and are generated by the 'pfstore' utility:

# pfstore -f /usr/local/etc/owampd.pfs testuser

Then, run owampd so that it loads the pfs file (using the -c option).
Repeat the same pfstore action on the client machine, and then ping the server:

# owping -A A -u testuser -k /usr/local/etc/owampd.pfs

and this works.

Monday, May 25, 2009

Time loop in VMware guest

If the guest VM should always boot with the same date/time on the RTC, two actions must be done:

1. Edit the .vmx file of the guest VM, and add a line like this:

rtc.starttime = "1238765436"

where the number is a 1970-based epoch, in seconds.

2. Boot the guest VM, and run VMtools. Disable the host-to-guest time synchronization, and reboot the guest VM.

Then, the guest VM will always boot with the same date and time in the calendar.

Wednesday, May 20, 2009

Setup a Samba server


Edit /etc/samba/smb.conf and add set the basic settings in the 'global' section. The only important item is the "security=user" option.

The important thing is to enable the samba users with:

# smbpasswd -a

which adds (-a) a new user to the samba user database, and sets a new password to it.
Not doing this causes a 'permission denied' error in the client when attempting to mount it.

The syntax to mount a samba share is:

# mount -t cifs -o user=,pass= //server/resource

In the /etc/samba/smbusers file, it is possible to map sambe user names to UNIX user names, but it is not required to do so. By default, samba does a one-to-one mapping.

If we want to announce the samba shares on the network, it is necessary to start the 'nmb' service. Use the normal chkconfig interface to start smb and nmb on startup:

# chkconfig --add nmb
# chkconfig --level 3 nmb on
# chkconfig --level 4 nmb on
# chkconfig --level 5 nmb on




Tuesday, May 12, 2009

Set up VMware server on a x86_64 system

Setting up a 1.0.8 VMware server on a 64-bit PC creates some problems because VMware is distributed in binary form only (rpm packages) for 32-bit i386 architectures .

In my case, the target distribution is a Fedora 10 x86_64, so the first step was installing the 32-bit base libraries, that is, glibc, X11, etc. This can be easily done with the 'yum' installer:

# yum install glibc.i686 libgcc.i386 lidstdc++.i386 zlib.i386
# yum install libX11.i386 libXtst.i386 libXrender.i386 libXt.i386

Also, some Perl modules are required for the VMware installer to work properly:

# yum install perl-ExtUtils-Embed

which is not 32-bit specific, of course.

Now, we are in a position where the VMware-server package may be installed:

# rpm -ivh VMware-server-1.0.8.i386.rpm

If we hadn't installed the i386 libraries, the vmware-server rpm could be installed anyway but problems would appear when running the vmware-config.pl script, which runs the vmware-ping utility, which is a 32-bit one.

Now, some patches to the vmware distribution must be installed. In my case, the vmware-any-any-117d could not be installed, as it returned an error when compiling the kernel modules (something about an already defined symbol). The update that worked fine was 'vmware-update-2.6.27-5.5-2.tar.gz' but this depends on the particular kernel version that runs in my target system.

Installing this update is quite straightforward, however, this tarball contains a compiled 'update' executable which is intended for 32-bit installations. I decided to delete it and recompile manually before running the update:

# tar xfz vmware-update-2.6.27-5.5-2.tar gz
# cd vmware-update-2.6.27-5.5-2
# rm update
# gcc update.c -o update
# ./runme-pl

The 'runme.pl' script automatically launches the 'vmware-config.pl' script. I accepted all the proposed default answers, except for the NAT networking, which I answered 'no', the host-only networking -also 'no'- and the TCP port to listen to, which is by default 904, but I changed it to 902 to match the default port the clients connect to (why this discrepancy in the defaukt values?).

If I had accepted the 904 port in the server side, then the client wouldn't connect to the server and would return a 'connection refused' error. Fortunately, the client connectiomn dialog accepts an optional port number after the server IP, i.e. myserver:904.

After this, the client still returned an error when attempting to connect to the client: 'login incorrect (username/password)'. After some investigations, I found out that the problem was in the PAM libraries. Again, only the 64-bit pam libraries were installed, but vmware expects the 32-bit libraries, so the user authentication failed. Unfortunately, the pam i386 and x86_64 libraries cannot coexist simultaneously, because there are some common files provided by both packages (i.e. the man pages). So, the workaround is to download the pam package and its dependencies, and do a force install:

# yumdownloader --resolve pam.i386
# rpm -ivh --force *.rpm

Also, we must configure the proper path to the pam libraries.
Edit /etc/pam.d/vmware-authd :

#%PAM-1.0
auth sufficient /lib/security/pam_unix2.so shadow nullok
auth required /lib/security/pam_unix_auth.so shadow nullok
account sufficient /lib/security/pam_unix2.so
account required /lib/security/pam_unix_acct.so

After all this, vmware server runs correctly and clients may connect.

To install the vmware client (a.ka. vmware-server-console), all I had to do is install the client rpm:

# rpm -ivh VMware-server-console-1.0.8-126538.i386.rpm
# vmware-config-server-console.pl

and that's all.

Tuesday, December 16, 2008

Using code sourcery to build a toolchain

Though it looks quite primising, it was very tough trying to build a cross toolchain using code-sourcery.

They provide prebuilt binary toolchains, but I none with the gcj compiler, so I had to use their build scripts to build my own toolchain.

The script they provide wasn't designed to run on any machine. In fact, it does not run on a machine different to the author's one. There are many hardwired directories and custom build is very tough. Though I tried to understand it, at the end some unexplainable errors appeared.

In conclusion, code sourcery for custom toolchains is not an option nowadays.

Testing the MIKA JVM

Downloading mika requires subversion as there seem to be no tarballs available for download.
Subversion requires port 3690, plus other tools.

Mika's repository is :
http://www.k-embedded-java.com/mika/trac/wiki/DownloadMikaNew

The dependencies are: jikes and jam.

Jikes is a java compiler from IBM. It is available as an rpm for FC4. No problems installing it.

Jam is a set of Ant scripts for building java applications. Just run configure, make, and make install. However, my FC4 has some broken links: /etc/alternatives/java_sdk_exports.
I had to edit the link to point to the installed JDK: /usr/lib/jvm/java-1.4.2-gcj

To build mika, I had to edit the Configuration/cpu/arm file, where the toolchain executables are defined. It wrongly assumes that the compiler was arm-linux-gcc, ignoring the CC variable.

To run the build process, type:
ant -DPLATFORM=default -DJAM.PLATFORM=arm-linux

Results are similar to the Skelmir's VM but with a different balance: setVisible is 3 times faster, but the screen creation is 3 times slower. The net performance is more or less the same.


Mika looks quite lightweight in terms of the flash footprint required.

Using crosstool-ng

The objective is to build a valid toolchain for ARM that supports gcj with the GTK peer classes.
The latest gcc is required (version 4.3) which has the latest GNU classpath version, and still evolving.

I tried several options to build a toolchain: crosstool, code-sourcery, etc, but crosstool-ng is the only one that was able to build a 4.3 toolchain without problems. The original crosstool was able to build a toolchain up to gcc 4.1.1/glibc 2.3.2. Newer versions of gcc or glibc did not compile.

Building and installing crosstool is very straightforward. Create a temporary directory, untar the tarball (crosstool-1.3.0) and run:

mkdir /usr/crosstool-ng (as root)
chown /usr/crosstool-ng

./configure --prefix=/usr/crosstool-ng
make
make install

and then add /usr/crosstool-ng/bin to the PATH variable.

The toolchain to be built is configured by running:
ct-ng menuconfig

There are some important hints:

1. The build tuple must be:
CT_BUILD=i686-pc-linux-gnu
which does not mean that the links i686-pc-linux-gcc -> gcc must be created. Crosstool-ng already creates its own wrapper scripts for this.

2. EABI
Defines how code is generated according to some specific conventions. gcc 4.3 is able to generate EABI-compliant code. All the user-space code must be compiled with the same EABI version, and the kernel must be compiled with EABI support if the user-space code is also EABI.
A toolchain may be created with EABI support, but this does not necessarily mean that the code generated is EABI. Some gcc flags control this. The glibc library was compiled using EABI, therefore the kernel must be compiled with EABI support, otherwise the 'init' program would not be loaded by the kernel.

3. Kernel headers
If we are using a new kernel (i.e. 2.6.1 or greater) then the best option is to use the sanitized kernel headers provided by the kernel's makefile. Crosstool-ng automatically invokes this makefile to generate the sanitized header set. By the way, using the latest 4.3 gcc, requires a late glibc (2.6 or 2.7), which in turn, requires a 2.6 kernel.

4. glibc ports
If the option "use glibc ports" is not enabled, the glibc compilation may return an error like "target arm is not supported". As of glibc 2.5, the architecture-dependent stubs of glibc are distributed as separate tarballs.

5. Dependencies
gcc 4.3 requires mrfp library version 2.3.2 or greater, which requires automake > 1.10 and autoconf > 2.60. There are no rpms for these versions for FC4, so I had to download the sources and install them manually

6. Enabling gcj/java support
Must enable the java language.
Also, the specific java/gtk support must be enabled in the gcc build flags:

CT_CC_EXTRA_CONFIG=

--enable-java-awt=gtk --enable-gtk-cairo --disable-gtktest
--x-libraries=/lib --x-includes=/include/X11

note that the GTK peer classes provided by GNU classpath require some X libraries, though the GTK compiled uses the Directfb frontend, not the X-lib frontend. It seems that it still uses some X libraries for rendering functionality or something like that. Hopefully, these libraries are not required in the target system. In particular, it requires the libXtst, libart, libXrender and libXrandr libraries.

It is important to set the pkg-config variables, so that the configure scripts of gcc and its subprojects are able to find the GTK libraries:

export PKG_CONFIG_PATH=/usr/lib/pkgconfig
export PKG_CONFIG_LIBDIR=/usr/lib/pkgconfig

the pkgconfig files (.pc) must be placed in these directories. Note that the 'prefix' keys in these files must be set to the location of the installed GTK files /usr.

there are GTK include files in different locations, such as: usr/lib/glib-2.0/include.

7. Restarting crosstool-ng
Crosstool has a nice feature that allows the process to be restarted at some specific stage, thus speeding up the trial and error build process. However, this functionality must be enabled in the menuconfig, and this functionality has some limitations:
1. changing some configuration items (i.e. gcc build flags) take no effect if the process is restarted. The entirre build process must be started from the beginning.
2. Do not break crosstool-ng while downloading the tarballs. If so, clean the tarballs directory as some files may be corrupted.

8. X libraries
Some GTK peer classes cannot be compiled because they require a GTK library compiled with xlib support. For instance, the cairo-xlib.h and gdk/gdkx.h include files are required.

After all these steps were solved, a toolchain with gcj/gtk support was built without errors.

Thursday, November 6, 2008

SIGIO terminates program

I have compiled the IPC/nt library (inter-process communications) for the i686 architecture, on a CentOS 5.2 release. Compilation was ok, but at runtime, the programs that link with this library die unexpectedly when receiving data from the IPC layer. The message shown is "I/O possible".

Of course, this did not happen on the original embedded systems this library was originally developed for, so I assume something has changed. There are two possibilities:

1. getpid() no longer returns the pid of the calling thread, but the pid of the entire process.
2. the default action of the SIGIO signal has changed, causing the program to terminate rather than ignoring the signal.

The mechanism that IPC/nt uses to work with asynchronous sockets is the following: a thread listening to SIGIO signals is created. Any asynchronous socket opened sets the owner of the SIGIO signals to the thread's pid (hence the getpid issue here). When data arrives (I/O is possible), a signal is sent to the thread which wakes up the other threads listening on the sockets using a mutex/condition variable.

Then I came across this note that explains what is happening (though it does not explain exactly what has changed):
If a nonzero value is given to F_SETSIG in a multi-threaded process running with 
a threading library that supports thread groups (e.g., NPTL), then a positive value
given to F_SETOWN has a different meaning: instead of being a process ID identifying
a whole process, it is a thread ID identifying a specific thread within a process.
Consequently, it may be necessary to pass F_SETOWN the result of gettid instead
of getpid(2) to get sensible results when F_SETSIG is used. (In current Linux threading
implementations, a main thread’s thread ID is the same as its process ID. This means that
a single-threaded program can equally use gettid(2) or getpid(2) in this scenario.)

So, I replaced the call to getpid() by a call to gettid(), which I have proven it is
also backwards compatible. It works fine.

Tuesday, July 22, 2008

Testing qemu

The goal is to run ARM software on a Linux PC.
I download the latest qemu, which is 0.9.1 today.

There isn't much fun here, just the usual stages:

./configure --target-list=arm-linux-user
make
make install (as root)

Qemu requires the run-time libraries to run. The quickest way is to explode an entire package release to a root directory:

# tpkg-explode main tempdir

Warning: the postinstall scripts of the packages are not executed, which is not very important for most of the packages but the library cache file. So I manually edit etc/ld.so.conf and add the library paths there /usr/TCSL/lib.

Then I rebuild the library cache:
# ldconfig -r //tempdir

and now I can run any ARM program:

# qemu-arm -L tempdir tempdir/usr/TCSL/bin/unsls

It works great, but it does not detect memory overwrites or leaks (who told me that it could?).
It's fine, but not as a memory checker for ARM, as I intended.

Cross-compiling Valgrind

Valgrind is available for other architectures other than the i386/PC. Unfortunately it is not ported to the ARM processor family, but it still can be useful for the PPC targets I use.

First, I download the latest version of valgrind (today is 3.3.1).

Before configuring, a word of advice: the prefix option does not work as usual. The absolute prefix path gets hardwired in the valgrind code so that valgrind is able to find other tools such as memcheck, etc. So, the prefix path must be used with the target distribution in mind. As I plan to install valgrind on a network directory and mount it via NFS from the target unit, I will set 'prefix' to
the name of the mountpoint.

The reason for not installing valgrind on the target is because the size of the file is quite big (more than 50MB) as valgrind requires that his own binary files are not stripped. This is explained in the README_PACKAGERS file.

Apart from setting the usual CC variable:
/configure --host=ppc-linux --prefix=/ppc/tools/valgrind --disable-tls
make
make install

TLS is not provided by my kernel version, so it is disabled here.

At this point, valgrind runs but it reports an error that it is unable to do some binary code substitutions (i.e. the strlen function). It suggests that the linker may be stripped /lib/ld-·so, which is true.

Having the unstripped version of 'ld' requires compiling the entire glibc RPM. For my platform, the glibc is installed as an RPM. Then, I download and install the source RPM.

The binary files get automatically stripped by rpmbuild at the end of the %install stage. The "__os_install_post" macro contains the script that must be run at that point. This scriptlet calls the brp-strip-shared script, which lives under /opt/eldk/usr/lib/rpm, and strips all the shared libraries found in the installation directory. Unfortunately, my installation of ppc-rpm does not allow this scriptlet to be edited, so the only option is to add a condition at the top of the brp-strip-shared scripts so that if the DONT_STRIP_SHARED variable is set, then exit the script immediately. The spec file must be edited so that this variable is defined.

Then, I modify the glibc.spec file where, at the end of the %install section, there is a piece of code that strips all shared libraries but the 'libpthread' library. I just add the 'ld' library as an exception to the strip process, and then I rebuild the RPM.

export PATH=/opt/eldk/usr/ppc-linux/bin/:$PATH
ppc-rpmbuild -ba glibc.spec

Do not forget to increase or change the version of the RPM so that it is installed in the target successfully.

A simpler workaround is to copy the ld-x.y.z.so file manually, in this case, use 'cp -f' as the file is always in use.