A Programmer's Ramblings

I just want to talk about programming.

Calling C Standard Library functions from assembly

I recently began delving into x86 assembly language using MASM and VS2015 for a Computer Organization class. In this class, all I/O is done using the basic procedures provided by the textbook author's library. This is really quite limiting, so let's look at calling some C Standard Library functions from assembly.


This post assumes at least some familiarity with x86 assembly language. Source and VS2015 project files are available on my GitHub.

Microsoft changed the C runtime and libraries in VS2015, so I had to reference this thread on StackOverflow detailing which libraries to include.

  • You'll need to add these libraries to your dependencies: libcmt.lib, libvcruntime.lib, libucrt.lib, legacy_stdio_definitions.lib. Alternatively you could use includelib to include these libraries in your assembly file.
  • Specify C calling convention for your main procedure using PROC C

Let's get started!

I'm going to provide in-depth explanations of most code, for my benefit as much as the reader's.


Setting up

First, we need to tell the assembler our target architecture (386), our memory model (protected mode), and our calling convention (Microsoft stdcall, which we'll override later). We'll also need to include our libraries.

.386
.model flat, stdcall

includelib libcmt.lib
includelib libvcruntime.lib
includelib libucrt.lib
includelib legacy_stdio_definitions.lib
Here we tell the linker to look for our external functions, and set up a few variables in our .data segment:
extern printf:NEAR
extern scanf:NEAR

.data
prompt BYTE "Enter an integer: ",0;
input_format BYTE "%d",0
text BYTE "You entered: %d", 0
integer DWORD ?

Calling conventions

The calling convention determines how procedures are called, specifically:

  • The order of and method by which arguments are passed: registers, the runtime stack, or memory.
  • The registers that need to be preserved by called procedures.
  • How the stack pointer is restored after the call (is it the called or calling procedure's responsibility?)
  • How return values are handled.

We'll be using the C calling convention.

.code
main PROC C                    ; Specify "C" calling convention
This means we'll pass our arguments on the stack in reverse order. Registers EAX, ECX, and EDX are caller-saved, the rest are callee-saved, and we'll place our return value in the EAX register. Our calling procedure will clean up the stack by adjusting the stack pointer (ESP) after the callee returns.


Calling printf

Sounds like a lot of work, right? It's not actually that bad. Here's how to call printf:

  1. Push the memory address of a null terminated string onto the stack.
  2. Call printf.
  3. Adjust the stack pointer by four bytes to remove the DWORD argument.
push offset prompt
call printf
add esp,4


Calling scanf

So now that we have 'Hello world' written in assembly, let's try calling scanf. Remember that arguments go on the stack in reverse order, so we push the address of our destination variable, then the format string for scanf.

push offset integer
push offset input_format
call scanf
add esp,8
Note that 8 bytes were added to the stack pointer to remove our two DWORD arguments. Let's display the variable we read:
push integer
push offset text
call printf
add esp,8


Finishing up

We finish by exiting main cleanly with the equivalent of return 0;. Remember that C calling convention has us return values in EAX:

mov eax,0
ret
And that's it!