Making Linux system calls

Linux system calls are typically performed via a C interface. D can call these just like any other C library functions (see the Using external libraries recipe in Chapter 1, Core Tasks). Here, we'll write a "Hello World" program using the standard C function for the system call, and say "hello" using inline assembly to demonstrate how that can be done as well.

How to do it…

Making Linux system calls with inline assembly is quite different than making them with the C interface. First, we will look at how it is done with the function, then we'll translate it to the assembly.

With the C interface

Let's make Linux system calls by executing the following steps:

  1. Import the appropriate header from the core.sys.posix package, or if it is not present, write the prototype with extern(C).
  2. Call the function like you would call any other function.

The code is as follows:

import core.sys.posix.unistd; // analogous to #include <unistd.h>
string hello = "Hello, world!";
write(1 /* stdout file descriptor */, hello.ptr, hello.length);

With inline assembly

  1. Decompose the arrays you need into separate variables for their pointer and lengths.
  2. Use version blocks for each supported architecture and OS.
  3. Write the code using the Linux reference documentation for the appropriate registers and numerical arguments.
  4. End each group of versions with static assert(0) to trigger a compile error on unsupported systems.

The code is follows:

string hello = "Hello, world!";
auto hptr = hello.ptr;
auto hlength = hello.length;
version(linux) {
version(D_InlineAsm_X86)
asm {
    mov ECX, hptr;
    mov EDX, hlength;
    mov EBX, 1; // stdout
    mov EAX, 4; // sys_write
    int 0x80; // perform the operation
} else version(D_InlineAsm_X86_64)
asm {
    mov RSI, hptr;
    mov RDX, hlength;
    mov RDI, 1; // stdout
    mov RAX, 1; // sys_write
    syscall; // perform the operation
} else static assert(0, "Unsupported Processor for inline asm");
} else static assert(0, "Unsupported OS for this function");

When you run the program, it will print Hello, world!.

Note

Inline assembly with the syntax given here only works with the DMD and 1DC compilers on x86 processors (32 or 64 bit). The GDC compiler has different and changing support for the assembly language.

How it works…

Standardized C headers are available in druntime under the core package. The core.stdc.* package has the standard C library headers. The core.sys module has standard headers for the operating system.

The following table has some illustrative examples of a header file and its corresponding D module names:

C header

D module

#include <stdio.h>

import core.stdc.stdio;

#include <stdlib.h>

import core.stdc.stdlib;

#include <unistd.h> /* Unix header */

import core.sys.posix.unistd;

#include <sys/socket.h>

import core.sys.posix.sys.socket;

#include <epoll.h> /* Linux specific */

import core.sys.linux.epoll;

If you are unsure whether a header is standardized in Posix or is specific to a particular system such as Linux, you can check the man page for a Conforming to section, or simply try to import the module from core.sys.posix first, and if that doesn't work, try again with core.sys.linux.

If you need a function that is not provided in any core module, you can still call it by copying the function and struct prototypes into your D program, marking them extern(C), as shown in the following code:

/* import the basic type definitions */
import core.sys.posix.sys.types;
/* Copy the function prototype from the system documentation */
extern(C) ssize_t write(int fd, const void* buf, size_t count);
// we can now call write!

D also has built-in support for the inline assembly code. While the main compilers currently have differing support for inline assembly, here, we're using the syntax for the DMD compiler, which is based on the Intel assembly syntax.

A block of assembly code is denoted by the asm { } structure. Inside, the syntax remains similar to D—comments remain in the same D forms, instructions must be separated by semicolons, and D identifiers are still usable. Unlike most assemblers, D's inline asm syntax is case sensitive. Instruction names must be in lowercase and register names must be in uppercase.

The assembly language code is rarely portable, so it is also advisable to always put asm blocks inside version blocks, and be as specific as you can be, at least based on the processor family. Here, we broke it down by the operating system, then by 32-bit and 64-bit asm on the x86 processor because the code needs to be different in each case. The else static assert function at the end of each group of versions ensures that an unimplemented configuration results in a compilation error rather than the silently buggy or incomplete code. Any time you use version statements, consider terminating the group with else static assert(0, "message") for future maintenance.

See also

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.221.163.13