DEV Community

BossySmaxx
BossySmaxx

Posted on

Understanding Runtimes: From C to Modern Languages

In the era of modern software development and programming, the term "runtime" can mean different things depending on the context and the language being discussed. I am here to clarify these differences, focusing on how runtimes work in C compared to more modern languages like Java or Python. I intend to keep this article focused for beginner programmers, so I will avoid delving into complex concepts.

What is a Runtime?

At its core, a runtime is a program itself that reads and executes code written by a developer. But it gets confusing when some developers use runtime with C language.

Modern Language Runtimes

In languages like Java or Python, the runtime is a program itself that reads your myfile.js file, that's why you run nodejs programs like: node myfile.js and v8 engine( is the JavaScript engine, it parses and executes JavaScript code.) manages everything for it whether you create a new file, spin up a child process and etc and most importantly you cannot do anything which v8 doesn't allow you to do.
But When you run a c program you don't do c myfile.c you just have to compile it once and now you don't need gcc anymore just run it directly.

The C "Runtime"

In C, there isn't a separate program running alongside your code in the same way as in Java or Python. Instead, what's often called the C "runtime" is actually a set of statically inserted code and instructions added during compilation. It is a minimal set of instructions included in the final binary to handle certain necessary tasks at CPU/OS level. It handles stack frame creation and teardown for function calls (using instructions like PUSH, POP, CALL, RET in assembly). Even that can be override by providing your own __start function using inline assembly, giving developers complete control over the program's entry point and initialization.

void __start() {
// Custom entry point, no standard library initialization
// You have no access to argc and argv here unless you access them manually from registers
// you can create you own custom stack setup, initialization and etc here.

// Exit directly using a syscall
asm("mov $60, %rax; mov $0, %rdi; syscall"); // exit(0) syscall
}
Enter fullscreen mode Exit fullscreen mode

This doesn't look like runtime at all it's just some Assembly language code added by compiler so developers don't have to.

The Power and Responsibility of C

In C, you can invoke system calls directly using inline assembly to interact with the kernel in ways not typically allowed by the OS, that's how malwares are created. Inline assembly allows developers to write assembly language instructions within C code. This is often used for performance-critical code or to access specific hardware features.

Inline Assembly in C

  • Inline assembly allows developers to write assembly language instructions within C code. This is often used for performance-critical code or to access specific hardware features.
  • It provides a way to execute CPU instructions directly.

Direct Interaction with the Kernel

  • using inline assembly, a programmer can invoke system calls directly without going through higher-level libraries.
  • For example, we can use inline assembly to set up registers with the appropriate parameters for a system call and then trigger it.
  • Since inline assembly allows for low-level control over system resources, it can be used to bypass security mechanisms or manipulate the kernel directly. This is how malware can perform unauthorized actions, such as accessing protected memory, intercepting system calls, or manipulating processes and their memory.
  • Malware can exploit vulnerabilities in the OS or use these low-level interactions to perform tasks like keylogging, privilege escalation, or stealth operations.

In linux C has a FLAG that allows you to directly write file data to a storage device, bypassing some of the kernel’s caching mechanisms, is called O_DIRECT flag which is used in combination with the open and write system calls. This flag ensures that data is not buffered in RAM or managed by kernel in kernel space this directly writes the data to Hard Drive, JVM won't allow you to that, and it's just one simple example.
here's a simple example:

asm volatile (
"syscall"
: "=a" (written)
: "0" (1),
"D" (fd),
"S" (buffer),
"d" (BLOCK_SIZE)
: "rcx", "r11", "memory"
);
Enter fullscreen mode Exit fullscreen mode

*Note: * (written) is variable created inside main(), (1) is syscall number for write, (fd) is where file will be written i.e int fs = open("path.log",O_WRONLY; (BLOCK_SIZE) is another variable name. It's more complex than that.

The Evolution of Runtimes

It's important to understand that the concept of runtime has evolved over the years. The C "runtime" of the '70s is very different from the robust runtime environments we see in languages from the 2000s. This evolution can lead to confusion when discussing runtimes, especially between developers familiar with different eras of programming.

Conclusion

I think people are now comparing the runtime of 1970s with the runtimes of 2000s, which is getting new developers confused with old developers.
Solving a specific problem is the major task of any programming language you don't want to write a whole framework to create APIs in C we have nodejs and it's good at it and you don't need to write bare metal code in javascript because we already have C and it's fabulous at it. Why Re-invent the wheel, let's use use the wheels and create a fabulous car, unless you don't want to drive it on mars.

Top comments (0)