Is Assembler Portable Between Linux Distros

Is assembler portable between Linux distros?

At a very high level, the ABI consists of { instruction set, system calls, binary format, libraries }.

Distribution as .s may free you from the binary format. This is still rather pointless, because you are fixed to a particular ISA and still need to use libraries and/or make system calls. Libraries vary from distribution to distribution (although this isn't really that bad, especially if you just use libc) and syscalls vary from OS to OS.

Which Linux distribution should I go for learning C/C++/Assembly in Linux

You can't really go wrong with any of the major ones. Personally I use Debian, but Fedora and OpenSUSE are good choices as well.

I would also like to point out that you can use C# to create portable GUI applications. Have a look at Mono and Gtk#. I have developed quite a few Gtk# apps and they usually run flawlessly on Windows and Linux, with very little work on my part. It might not be a bad introduction to coding on Linux, as you will be able to use a familiar language.

Building linux binaries for multiple platforms

You may try to focus on a few major platforms rather than individual distributions. What I mean by that is to build on what I call the "foundational distros" (Debian and RedHat) and hope for the best on the others.

Most likely, Debian binaries (that are statically linked) will run just fine on Ubuntu and Mint and other Debian derived distributions. RedHat binaries will probably run on Centos, Scientific Linux and SuSE Linux. If you care about less popular distros (Say you have a lot of customers running some uncommon Linux), and neither your Debian or RedHat executable works on them or can be made to work somehow, then setup a VM of that distro and build one executable specifically for that flavor.

I've taken this approach in the past and it has worked well.

does assembler output differ between operating systems?

It's a difficult question to answer. If I compile the following code:

void f() {
int x = 0;
x = x + 1;
}

to a .o file (i.e. not linked) on both platforms, would I exepect the x86 output to be the same?

Answer: Possibly. But I wouldn't be suprised if it wasn't.

Can you make an OS only using assembly language?

The correct but useless answer is - yes, it's possible. Anything C/C++ does, assembly can do, too. If your next question is - how, the only possible answer would be "read a book". This is a small question with a book length answer. Yes, anything you can code in C/C++ you can also code in assembly... but it will take you 10 times longer, especially with debugging.

That aside, putting together a working OS in assembly will be an order of magnitude more complicated and time consuming than setting up cross compilation. If that is a challenge for you, maybe you shouldn't tackle a whole OS just yet.

making proprietary ELF binaries portable on Linux

Searching all linked libraries and their dependencies and include them in a subdirectory of the binary and changing the Library-Path to that directory.

This would work for most shared libraries, but will not work for libc.so.6 (which is the one most likely to give problems if your target system doesn't have new enough version).

The reason: glibc consists of over 200 separate shared libraries, which have un-versioned binary interfaces between them, and do not have a stable ABI between them. Because of this, all parts of glibc must come from the same build. One of these parts is libc.so.6. Another is ld-linux.so. An absolute path to the latter is hard coded into every dynamic executable. The end result: if you supply your own copy of libc.so.6, and if that copy doesn't match /lib/ld-linux*.so.2 present on the system, then you'll see very strange crashes which you would have very hard time to explain or debug.

Relinking the libraries statically into the binary file to one big executable.

That can't work on any UNIX system other than AIX: they all consider a.out and foo.so to be the final link product, which can't be linked any further.

There exists statifier, which does create a (huge) static executable out of a dynamic one. I have no experience using it.

Why do we need to compile for different platforms (e.g. Windows/Linux)?

Even though CPU is the same, there are still many differences:

  • Different executable formats.
  • Different calling conventions might be used. For example Windows x64 passes integer args in different registers than the x86-64 System V ABI and has several other significant differences, including call-preserved xmm6..15 in Windows, unlike other x86-64.
  • Different conventions regarding stack structure. Some systems have a concept of "red zone" to help compiler generate shorter code. Execution environment has to honor such concept to avoid stack corruption.
  • Programs are linked against different standard libraries with different ABIs - field order might differ, additional extension fields might be present.
  • In both C and C++ some data types have OS dependent sizes. For example on x86_64 long is 8 byte on Linux, but 4 bytes on Windows. (Type sizes and required alignments are another part of what makes an ABI, along with struct/class layout rules.)
  • Standard libraries can provide different set of functions. On Linux libc provide functions like snprintf directly, but on Windows snprintf might be implemented as static inline function in a header file that actually calls another function from C runtime. This is transparent for programmer, but generates different import list for executable.
  • Programs interact with OS in a different way: on Linux program might do system call directly as those are documented and are a part of provided interface, while on Windows they are not documented and programs should instead use provided functions.
  • Even if two OS rely on program doing system calls directly, each kernel has its own set of available system calls.

Even if a Linux program only calls the C library's wrapper functions, a Windows C library wouldn't have POSIX functions like read(), ioctl(), and mmap. Conversely, a Windows program might call VirtualAlloc which isn't available on Linux. (But programs that use OS-specific system calls, not just ISO C/C++ functions, aren't portable even at a source level; they need #ifdef to use Windows system calls only on Windows.)

  • Not OS related, but programs compiled by different compilers might not be interoperable: different standard libraries might be used, things like C++ name mangling might be different, making it impossible to link libraries against each other, C++ exception implementation might be non-interoperable.
  • Different filesystem structure. Not only there is a difference between "" on Windows and "/" on Unix-likes, but there are "special files" that might or might not be present like "/dev/null".

In theory everything listed here can be resolved: custom loaders can be written to support different executable formats, different conventions and interfaces do not cause problems if the whole program uses the same set of them. This is why projects like Wine can run Windows binaries on Linux. The problem is that Wine has to emulate functionality of Windows NT kernel on top of what other OSes provide, making implementation less efficient. Such program also have problems interacting with native programs as different non-interoperable interfaces are used.

Source-compatibility layers like Cygwin can be inefficient, too, when emulating POSIX system calls like fork() on top of the Windows model. But in general Cygwin has an easier job than WINE: programs need to be recompiled under Cygwin. It doesn't try to run native Linux binaries under Windows.

Writing portable Scheme code

Writing standard-compliant Scheme imposes strict limits on what you can do. The R5RS standard is very small, and doesn't include such basic things as error handling, or even detecting which compiler/interpreter your code is running on. The R6RS standard is more extensive, but not widely implemented. Therefore, writing a Scheme program that will run on whatever Scheme interpreter or compiler that happens to be installed on the user's machine is difficult.

That doesn't matter too much, however, because Scheme is not widely installed. Chances are that your end user will not have any Scheme interpreter installed, except maybe in the guise of libguile, but that's a C library.

You can have platform portability by targeting a specific implementation of Scheme, which chances are you'll have to do anyway because you'll need to rely on some implementation's extensions to the Scheme standard to get any work done.

Chicken Scheme has a compiler that produces small executables, and it purports to run on both Windows and Unix. I've only used it on Linux, however.

The commercial Chez Scheme also has a compiler that produces executables on both Windows and Linux, but I've never used any version of Chez Scheme on any platform.

Racket can produce executables on Windows, Linux, and MacOS. However, the language deviates from Scheme considerably. For example, lists are immutable in Racket. Racket has a big library that includes things like networking and GUIs, all fully portable between operating systems. The compiler produces big executables.

SISC runs on the JVM, making it portable to anything that Java runs on. However, it's an interpreter, not a compiler.

All of the above have foreign function interfeces to C (or to Java in the case of SISC).

Run a C executable in another distro

At the end, I solved it compiling the GSL library without root privileges.
I uncompressed the sources in a folder in the home directory, created a _build directory, and ran ../configure, and then make.

I copied the files of the .libs directory that was created inside _build in a new ~/path/lib directory, and used:

find -name "*.h" -type f -exec cp {} ~/path/include/gsl \;

To copy all the header files generated in the GSL source folder (surely there was a better way to do that).

I then tried to set the environment variables for gcc (C_INCLUDE_PATH, LIBRARY_PATH) but for some reason I wasn't able to save them (used and export, tried to change them in the ~/profile and ~/.bash_profile files).

So, I used the -I and -L gcc options to link the two folders. It worked this way.



Related Topics



Leave a reply



Submit