why we must recompile a c source code for a different os on the same machine?

送分小仙女□ 提交于 2021-01-27 05:32:13

问题


When I compile my c source code (for example in a Linux environment) the compiler generates a file in a "machine readable" format.

  • Why the same file is not working on the same machine under a different operating system?
  • Is the problem about the way we "execute" this file?

回答1:


Sometimes it will work, depending on the format and the libraries that you use, etc.. For example, things like allocating memory or creating a window all call the OS functions. So you have to compile for the target OS, with those libraries linked in (statically or dynamically).

However, the instructions themselves are the same. So, if your program doesn't use any of the OS functions (no standard or any other library), you could run it on another OS. The second thing that is problematic here is executable formats.. Windows .exe is very different from for example ELF. However, a flat format that just has the instructions (such as .com) would work on all systems.


EDIT: A fun experiment would be to compile some functions to a flat format (just the instructions) on one OS (e.g. Windows). For example:

int add(int x, int y) { return x + y; }

Save just the instructions to a file, without any relocation or other staging info. Then, on a different OS (e.g. Linux) compile a full program that will do something like this:

typedef int (*PFUNC)(int, int); // pointer to a function like our add one

PFUNC p = malloc(200); // make sure you have enough space.
FILE *f = fopen("add.com", "rb");
fread(p, 200, 1, f); // Load the file contents into p
fclose(f);
int ten = p(4, 6);

For this to work, you'd also need to tell the OS/Compiler that you want to be able to execute allocated memory, which I'm not sure how to do, but I know can be done.




回答2:


I have been asked what is an ABI discrepancy. I think it's best to explain over a simple example.

Consider a little silly function:

int f(int a, int b, int (*g)(int, int))
{
    return g(a * 2, b * 3) * 4;
}

Compile it for x64/Windows and for x64/Linux.

For x64/Windows the compiler emits something like:

f:
sub         rsp,28h
lea         edx,[rdx+rdx*2]
add         ecx,ecx
call        r8
shl         eax,2
add         rsp,28h
ret

For x64/Linux, something like:

f:
sub    $0x8,%rsp
lea    (%rsi,%rsi,2),%esi
add    %edi,%edi
callq  *%rdx
add    $0x8,%rsp
shl    $0x2,%eax
retq   

Allowing for different traditional notations of assembly language on Windows and Linux, there obviously are substantial differences in the code.

The Windows version clearly expects a to arrive in ECX (lower half of the RCX register), b in EDX (lower half of the RDX register), and g in the R8 register. This is mandated by the x64/Windows calling convention, which is a part of the ABI (application binary interface). The code also prepares arguments to g in ECX and EDX.

The Linux version expects a in EDI (the lower half of the RDI register), b in ESI (the lower half of the RSI register), and g in the RDX register. This is mandated by the calling convention of System V AMD64 ABI (used on Linux and other Unix-like operating systems on x64). The code prepares arguments to g in EDI and ESI.

Now imagine that we run a Windows program which somehow extracts the body of f from a Linux-targeted module and calls it:

int g(int a, int b);

typedef int (*G)(int, int);
typedef int (*F)(int, int, G);

F f = (F) load_linux_module_and_get_symbol("module.so", "f");
int result = f(3, 4, &g);

What is going to happen? Since on Windows functions expect their arguments in ECX, EDX and R8, the compiler will place actual arguments in those registers:

mov         edx,4
lea         r8,[g]
lea         ecx,[rdx-1]
call        qword ptr [f1]

But the Linux-targeted version of f looks for values elsewhere. In particular, it is looking for the address of g in RDX. We have just initialized its lower half to 4, so there are practically nil chances that RDX will contain anything making sense. The program will most likely crash.

Running Windows-targeted code on a Linux system will produce the same effect.

Thus, we cannot run 'foreign' code but with a thunk. A thunk is a piece of low-level code which rearranges arguments to allow calls between pieces of code following different sets of rules. (Thunks may probably do something else because the effects of ABI may not be limited by the calling convention.) You typically cannot write a thunk in high-level programming language.

Note that in our scenario we need to provide thunks for both f ('host-to-foreign') and g ('foreign-to-host').




回答3:


There are two things of importance:

  • the development environment;
  • the target platform.

The development environment's compiler generates an object file with machine code and references to functions and data not contained in the object moule (not defined in the source file). Another program, the linker, combines all your object modules, plus libraries, into the executable. Please note:

  • The format of the object module is in principle platform Independent, although standards exist for platforms to easily combine object modules produced by different compilers for the platform. But that doesn't need to be; a fully integerated development environment can have its own "standard".

  • The linker can be a program from any manufacturer. It needs to know the format of the object modules, the format of the libraries and the desired format of the resulting excutable. Only this latter format is platform dependent.

  • The libraries can be in any format, as long as there is a linker that can read them. BUT: the libraries are platform dependent as the functions in the library call the API of the operating system.

A cross-development environment could for example generate object modules that are Windows compatible, then a linker can link them with libraries in Windows compatible format, yet targeted for Linux (using Linux OS calls) and deliver a Linux executable. Or any combination you like (Linux object format, windows library format, Windows executable; ...).

To summarize, the only truly platform dependent items are the functions in the libraries, as these call the OS, and the resulting executable as that is what the OS will load.

So, to answer the question: no, there is not necessarily a need to compile a source file for different platforms. The same object module can be linked for Linux (using Linux targeted libraries and creating a linux-format executable), or for Windows (using Windows targeted libraries and creating a Windows-format executable).




回答4:


  1. Different operating systems will use different Application Binary Interfaces (ABIs), this includes code needed for function entry and exit
  2. Certain language features may need direct platform support (things like thread local storage come to mind)
  3. The linker will generally link automatically to the toolchain specific standard library. This will need to change between Operating systems is for no other reason that each operating system has its own set of system calls.

Having said that, the Wine project is a good example where all these issue have been wrapped up to try to make windows code run on linux.




回答5:


You are right, compiling translates your source code into machine readable code, e.g. into x86 machine code.

But there is more to it than that. Your code often not only uses machine code that is compiled into your executable file, but also references operating system libraries. All modern operating systems supply different APIs and libraries to the programs. So if your program is built to work with e.g. some Linux libaries and is then executed under an operation system that doesn't contain these libraries it will not run.

The other thing here is the executable file format. Most executable files contain more than just executable machine code, but also some metadata, e.g. icons, information about how the file is packed, version numbers and quite a bit more. So by default, if you run e.g. a Windows .exe file on Linux, the operating system would not be able to handle that different file format correctly.

Systems like Wine add the missing libraries and are able to handle the different executable file formats, thus allowing you to run e.g. a Windows .exe file on Linux as if it was run on Windows natively.




回答6:


There are several good, general answers here. I'll give you a very specific example.

A x86 machine can easily run printf("Hello world") both 32bit Linux and DOS, if the C file is compiled for each platform.

One of many major differences between operating systems is how a program instructs the operating system to provide the services it does. Here is how you ask Linux to print a string:

msg db "Hello world"   # Define a message with no terminator
mov     edx, 11        # Put the message length in the edx register
mov     ecx, msg       # Put the message address in ecx
mov     ebx, 1         # Put the file descriptor in ebx (1 meaning standard output)
mov     eax, 4         # Set the system call to 4, "write to file descriptor"
int     80h            # Invoke interrupt 80h to give control to Linux

Here is how you ask DOS to print the same string:

msg db "Hello world$"  # Define a message terminated by a dollar sign
mov dx, msg            # Load the message address into dx
mov ah, 9              # Set the system call number to 9, "print string"
int 21h                # Invoke interrupt 21h to give control to DOS

They both use the same kind of basic, machine readable and executable instructions, but the directions are as different as English and Chinese.

So can't you teach Linux how to understand directions intended for DOS, and run the same file on both? Yes you can, and that's what DosEmu did back in the day. It's also how Linux+Wine runs Windows software, and how FreeBSD runs Linux software. However, it's a lot of headache and additional work, and may still not be very compatible.




回答7:


I post this reply to Andrey's discussion about ABIs as an answer because it is too much for a comment and requires the formatting of an Answer.

Andrey, what you are showing has nothing to do with Linux or Windows. It is an example of a development environment using certain conventions. All object modules and modules in libraries must adhere to these conventions, and nothing else. It isn't Linux or Windows that expect values in certain registers, it is the development environment.

The following is the more standard way of C calling conventions (Visual Stdio 2008). In all cases, the caller must evaluate parameters right-to-left as per the C standard:

int f(int a, int b, int (*g)(int, int))
{
    push        ebp  
    mov         ebp,esp 
        return g(a * 2, b * 3) * 4;
    mov         eax,dword ptr [ebp+0Ch] 
    imul        eax,eax,3 

    push        eax  
    mov         ecx,dword ptr [ebp+8] 
    shl         ecx,1 
    push        ecx  
    call        dword ptr [ebp+10h] 
    add         esp,8 
    shl         eax,2 

    mov         esp,ebp 
    pop         ebp  
    ret
}
  • The caller pushes the parameters right-to-left and calls the callee

  • the callee saves the stack frame pointer, usually ebp on Intel, and adds to esp for local storage (none here)

  • The callee references the parameters relative to ebp

  • The callee performs its function

  • The callee restores ebp and returns

  • The caller removes the parameters of the call from the stack, e.g. add esp,8

Again, it is the development environment that dictates these conventions, not the OS. The OS may have its own conventions for applications to request services. These are then implemented in the OS-targeted libraries.



来源:https://stackoverflow.com/questions/30574728/why-we-must-recompile-a-c-source-code-for-a-different-os-on-the-same-machine

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!