Skip to content

chaimleib/maclfs

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

8 Commits
 
 
 
 
 
 
 
 

Repository files navigation

Mac LFS

The goal of this project is to cross compile Linux on a Mac host. The only requirements are:

This guide is based on Linux from Scratch, Cross LFS - Embedded x86 and dslm4515's Musl-LFS. I updated to the latest packages as of 2021-01-08. The disk formatting tools were updated 2021-09-24.

Set up environment

Parted depends on glibc, which does not compile under macOS (dunno about musl). Compiling gdisk and sgdisk from source requires the brew packages ossp-uuid and popt. However, rather than descend into the dependencies and make the project independent of brew, I favored speed at this stage, since the goal is to learn about Linux, rather than macOS.

git clone https://github.com/chaimleib/maclfs
cd maclfs

To create the boot disk, we need gptfdisk and e2fsprogs. Building the header files will require GNU's version of sed.

brew install gptfdisk e2fsprogs gsed

Creating the target disk

# create a blank 15GB file
dd bs=4096 if=/dev/zero of=lfs.iso count=15728540

# create loopback device from blank file, and remember the device name
# In case you need to run this command again after a reboot and other
# partitions exist already, the `; exit` to awk behaves like `head -n1` to grab
# just the disk device name and not partition device names.
lfsDev=$(hdiutil attach -imagekey diskimage-class=CRawDiskImage -nomount lfs.iso |
  awk '{print $1; exit}')

# partition the disk
# -g                   Use GPT partitioning
# -n 1::+200M -t ef00  First partition should be 200MB EFI
# -n 2:: -t 8304       Rest should be ext4
# -c 2:'LFS Disk'      Name the main partition
sgdisk $lfsDev -g -n 1::+200M -t ef00 -n 2:: -t 8304

Now you have the necessary partition locations, but they aren't formatted yet.

# if you wish, verify the partitions.
sgdisk -p $lfsDev

Disk /dev/disk2: 31457080 sectors, 15.0 GiB
Sector size (logical): 512 bytes
Disk identifier (GUID): 87317754-AF95-4293-83D0-1F9F128FAA71
Partition table holds up to 128 entries
Main partition table begins at sector 2 and ends at sector 33
First usable sector is 34, last usable sector is 31457046
Partitions will be aligned on 2048-sector boundaries
Total free space is 2014 sectors (1007.0 KiB)

Number  Start (sector)    End (sector)  Size       Code  Name
   1            2048          411647   200.0 MiB   EF00
   2          411648        31457046   14.8 GiB    8304  LFS Disk

Now, format the main partition. Homebrew stopped linking the e2fsprogs into the PATH, so specify the full path to the binary:

/opt/homebrew/opt/e2fsprogs/sbin/mke2fs -t ext4 -L lfsdisk ${lfsDev}s2

Install extFS for Mac by Paragon Software ($39.95 or Free 10-day trial) to mount the disk image.

https://www.paragon-software.com/home/extfs-mac/

If you have to reboot, use the following command to create the loopback device again:

lfsDev=$( \
  hdiutil attach -imagekey diskimage-class=CRawDiskImage -nomount lfs.iso |
  awk '{print $1; exit}')

Then, extFS should be able to mount it.

Build directory, Packages and Patches

Give all users permissions to modify the disk:

LFS=/Volumes/lfsdisk
sudo chmod 777 $LFS

Create a directory that will hold the tools we need before we chroot or boot the new linux system. In other words, the tools in this folder will be used from macOS.

mkdir -pv $LFS/cross-tools/{bin,lib/pkgconfig}

Remember installing gsed a while back? Make a link to it named sed in our cross-tools, so that it is available when we isolate our PATH variable:

ln -s /usr/local/bin/gsed $LFS/cross-tools/bin/sed

Create a bashrc:

cat << "EOF" > $MLFS/bashrc
set +h
umask 022
MLFS=$HOME/maclfs
LFS=/Volumes/lfsdisk
LC_ALL=POSIX
PATH=$LFS/cross-tools/bin:$LFS/cross-tools/$LFS_TGT/bin:/bin:/usr/bin
MANPATH=$LFS/cross-tools/share/man:$MANPATH
PKG_CONFIG_PATH=$LFS/cross-tools/lib/pkgconfig
export MLFS LFS LC_ALL PATH MANPATH PKG_CONFIG_PATH
unset CFLAGS

export LFS_HOST="$MACHTYPE-cross-darwin"
export LFS_TGT=x86_64-mlfs-linux-musleabihf
export BUILD64=-m64
export LFS_CPU=k8-sse3
export LFS_ARCH=x86_64
EOF

Then create and run a script to isolate your environment variables and load your bashrc for you, make it executable, and run it. This reduces the chances that variables from your host environment can affect your build.

cat << "EOF" > $MLFS/lfs.env
#!/bin/bash
exec env -i \
  HOME=$HOME \
  TERM=$TERM \
  PS1='\u:\w\$ ' \
  /bin/bash --rcfile bashrc
EOF
chmod +x $MLFS/lfs.env
$MLFS/lfs.env

If you ever need to exit the new environment, type exit. To re-enter it, run $MLFS/lfs.env.

Cross toolchain

Prereqs for linux headers

Instead of wget, use curl -LJO to download each URL in packages.txt and patches.txt.

On 2021-09-24, I had HTTPS certificate issues with kernel.org and other source mirrors. For now, using curl works around it. Otherwise, I would have installed wget earlier, linked that and used it instead.

-LJO must precede each URL sent to curl. L means to follow redirects, and O means to save a file with an automatically-determined name. However, the default is to figure out the name from the URL which is not always the original file name. J says to use the name provided in the response headers.

The following commands assume that you have downloaded, decompressed and cd-ed into the packages listed in packages.txt. Some packages also require corresponding patches in patches.txt.

Linux (headers only)

Now make the linux headers.

make mrproper
make ARCH=$LFS_ARCH headers
make ARCH=$LFS_ARCH INSTALL_HDR_PATH=$LFS/cross-tools/$LFS_TGT headers_install

The above command failed to work on an HFS+ disk, and only succeeded inside the ext4 disk image.

Since kernel version 5.5, make headers_check has been a no-op.

Binutils

mkdir -v build
cd build
../configure \
   --prefix=$LFS/cross-tools/$LFS_TGT \
   --target=$LFS_TGT \
   --with-sysroot=$LFS/cross-tools/$LFS_TGT \
   --disable-nls \
   --disable-multilib
make configure-host
make
make install

Musl (headers only)

./configure --prefix=$LFS/cross-tools
make install-headers
cp -r $LFS/cross-tools/include/{bits,{byteswap,elf,endian,features}.h} /usr/local/include

# avoid "typedef redefinition with different types" error with macOS headers
sed -i '/typedef unsigned _Int64 uint64_t/d' /usr/local/include/bits/alltypes.h

GCC (static)

for p in mpfr gmp mpc; do
  tar -xf ../$p-*.tar.*
  mv -v $p-* $p
done
mkdir -v build
cd build
../configure \
  --prefix=$LFS/cross-tools \
  --build=$LFS_HOST \
  --host=$LFS_HOST \
  --target=$LFS_TGT \
  --with-sysroot=$LFS/cross-tools/$LFS_TGT \
  --disable-nls  \
  --disable-shared \
  --without-headers \
  --with-newlib \
  --disable-decimal-float \
  --disable-libgomp \
  --disable-libmudflap \
  --disable-libssp \
  --disable-libatomic \
  --disable-libquadmath \
  --disable-threads \
  --enable-languages=c \
  --disable-multilib \
  --with-mpfr-include=$PWD/../mpfr/src \
  --with-mpfr-lib=$PWD/mpfr/src/.libs \
  --with-arch=$LFS_CPU

# remember -j8 as applicable
make all-gcc all-target-libgcc
make install-gcc install-target-libgcc

Attempting to include isl here will cause install to fail.

musl

patch -Np1 -i ../wcsnrtombs_cve_2020_28928_diff.txt
./configure \
  CROSS_COMPILE=$LFS_TGT- \
  --prefix=/ \
  --target=$LFS_TGT
make
DESTDIR=$LFS/cross-tools/$LFS_TGT make install

GCC (final cross-compiler)

for p in mpfr gmp mpc; do
  tar -xf ../$p-*.tar.*
  mv -v $p-* $p
done

Now prepare the build directory and make a symlink to enable POSIX threads.

mkdir -v build
cd build
mkdir -pv $LFS_TGT/libgcc
ln -s ../../../libgcc/gthr-posix.h $LFS_TGT/libgcc/gthr-default.h
../configure \
  --prefix=$LFS/cross-tools \
  --build=$LFS_HOST \
  --host=$LFS_HOST \
  --target=$LFS_TGT \
  --with-sysroot=$LFS/cross-tools/$LFS_TGT \
  --disable-nls \
  --enable-languages=c \
  --enable-c99 \
  --enable-long-long \
  --disable-libmudflap \
  --disable-multilib \
  --with-arch=$LFS_CPU
make
make install

Do not attempt to also compile the c++ compiler at this stage. This leads to a dependency hell for files normally provided by glibc, but glibc has not been ported to work on macOS.

Findutils

The busybox Makefile requires us to have the GNU version of xargs, which is provided by GNU Findutils.

./configure --prefix=$LFS/cross-tools
make
make install

Pkg-config

To allow the menuconfig of the linux kernel to find our Ncurses library, Pkg-config needs to be present to point the linker in the right direction.

./configure --prefix=$LFS/cross-tools  \
            --with-internal-glib       \
            --disable-host-tool        \
            --docdir=$LFS/cross-tools/share/doc/pkg-config-0.29.2
make
make install

Ncurses

When compiling the linux kernel, the menuconfig step requires Ncurses.

sed -i '/LIBTOOL_INSTALL/d' c++/Makefile.in
sed -i s/mawk// configure

To bootstrap the compilation, we need to build the tic program first.

mkdir build
cd build
../configure
make -C include
make -C progs tic
cd ..

Now build the rest of Ncurses.

./configure --prefix=$LFS/cross-tools                \
            --mandir=$LFS/cross-tools/share/man      \
            --with-manpage-format=normal             \
            --with-shared                            \
            --without-debug                          \
            --without-ada                            \
            --without-normal                         \
            --enable-pc-files                        \
            --enable-widec
make
make TIC_PATH=$PWD/build/progs/tic install
echo "INPUT(-lncursesw)" > $LFS/cross-tools/lib/libncurses.so

In case there are libraries expecting the non-wide character libs, link them to the wide character versions.

for lib in ncurses form panel menu ; do
    rm -vf                    $LFS/cross-tools/lib/lib${lib}.so
    echo "INPUT(-l${lib}w)" > $LFS/cross-tools/lib/lib${lib}.so
    ln -sfv ${lib}w.pc        $LFS/cross-tools/lib/pkgconfig/${lib}.pc
done

Also, make sure that older programs looking for curses will be redirected to ncurses. Such programs would also require version 5, so we have to build again.

rm -vf                     $LFS/cross-tools/lib/libcursesw.so
echo "INPUT(-lncursesw)" > $LFS/cross-tools/lib/libcursesw.so
ln -sfv libncurses.so      $LFS/cross-tools/lib/libcurses.so
make distclean
./configure --prefix=$LFS/cross-tools  \
            --with-shared    \
            --without-normal \
            --without-debug  \
            --without-cxx-binding \
            --with-abi-version=5 
make sources libs
cp -av lib/lib*.so.5* $LFS/cross-tools/lib

Building the LFS system

Build variables

cat << EOF >> $MLFS/bashrc

export CC="${LFS_TGT}-gcc --sysroot=${LFS}"
export CXX="${LFS_TGT}-g++ --sysroot=${LFS}"
export AR="${LFS_TGT}-ar"
export AS="${LFS_TGT}-as"
export LD="${LFS_TGT}-ld --sysroot=${LFS}"
export RANLIB="${LFS_TGT}-ranlib"
export READELF="${LFS_TGT}-readelf"
export STRIP="${LFS_TGT}-strip"
EOF
source $MLFS/bashrc

LSB folder tree

Create the LSB (Linux Standard Base) folder structure.

cd $LFS
mkdir -pv bin boot dev etc home lib/{firmware,modules}
mkdir -pv mnt opt proc sbin srv sys
mkdir -pv var/{cache,lib,local,lock,log,opt,run,spool}
install -dv -m 0750 root
install -dv -m 1777 {var/,}tmp
mkdir -pv usr/{,local/}{bin,include,lib,lib64,sbin,share,src}

Config files

Set up config files.

List of mounted files.

ln -svf ../proc/mounts etc/mtab
cat > etc/passwd << "EOF"
root::0:0:root:/root:/bin/ash
bin:x:1:1:bin:/bin:/bin/false
daemon:x:2:6:daemon:/sbin:/bin/false
adm:x:3:16:adm:/var/adm:/bin/false
lp:x:10:9:lp:/var/spool/lp:/bin/false
mail:x:30:30:mail:/var/mail:/bin/false
news:x:31:31:news:/var/spool/news:/bin/false
uucp:x:32:32:uucp:/var/spool/uucp:/bin/false
operator:x:50:0:operator:/root:/bin/ash
postmaster:x:51:30:postmaster:/var/spool/mail:/bin/false
nobody:x:65534:65534:nobody:/:/bin/false
EOF
cat > etc/group << "EOF"
root:x:0:
bin:x:1:
sys:x:2:
kmem:x:3:
tty:x:4:
tape:x:5:
daemon:x:6:
floppy:x:7:
disk:x:8:
lp:x:9:
dialout:x:10:
audio:x:11:
video:x:12:
utmp:x:13:
usb:x:14:
cdrom:x:15:
EOF

Remember who last logged on.

touch var/log/lastlog
chmod -v 664 var/log/lastlog

Libgcc

Install a copy of libgcc, and strip it to make it smaller.

if [ "$LFS_ARCH" = 'x86_64' ]; then
  cp -v cross-tools/$LFS_TGT/lib64/libgcc_s.so.1 lib64
  $LFS_TGT-strip lib64/libgcc_s.so.1
else
  cp -v cross-tools/$LFS_TGT/lib/libgcc_s.so.1 lib
  $LFS_TGT-strip lib/libgcc_s.so.1
fi

Musl

patch -Np1 -i ../wcsnrtombs_cve_2020_28928_diff.txt
./configure \
  CROSS_COMPILE=$LFS_TGT- \
  --prefix=/ \
  --disable-static \
  --target=$LFS_TGT
make -j8
DESTDIR=$LFS make install-libs

Busybox

make distclean
make ARCH=$LFS_ARCH defconfig

Modify the default config to disable features that will not build under musl:

sed -i 's/\(CONFIG_\)\(.*\)\(INETD\)\(.*\)=y/# \1\2\3\4 is not set/g' .config
sed -i 's/\(CONFIG_IFPLUGD\)=y/# \1 is not set/' .config
sed -i 's/\(CONFIG_FEATURE_WTMP\)=y/# \1 is not set/' .config
sed -i 's/\(CONFIG_FEATURE_UTMP\)=y/# \1 is not set/' .config
sed -i 's/\(CONFIG_UDPSVD\)=y/# \1 is not set/' .config
sed -i 's/\(CONFIG_TCPSVD\)=y/# \1 is not set/' .config

The make script failed for me while trying to remove the contents of .tmp_versions/. To let that command succeed, add a dummy file there for it to remove, and then compile the package.

touch .tmp_versions/1
make ARCH=$LFS_ARCH CROSS_COMPILE=$LFS_TGT-
make ARCH=$LFS_ARCH CROSS_COMPILE=$LFS_TGT- CONFIG_PREFIX=$LFS install
cp -v examples/depmod.pl $LFS/cross-tools/bin
chmod -v 755 $LFS/cross-tools/bin/depmod.pl

Iana-etc

cp services protocols $LFS/etc

Make the system bootable

Create /etc/fstab

cat > $LFS/etc/fstab << "EOF"
# file-system  mount-point  type   options          dump  fsck
EOF

Linux kernel

make mrproper
make ARCH=$LFS_ARCH CROSS_COMPILE=$LFS_TGT- menuconfig

Releases

No releases published

Packages

No packages published