• Please review our updated Terms and Rules here

Help needed with writing an AIL/32 driver: dummy driver works in ASM, but not in C

digger

Experienced Member
Joined
Jul 9, 2008
Messages
395
Location
Amsterdam, the Netherlands
Hi.

As many of you know, John Miles released the source code to his Audio Interface Library (AIL) drivers some years ago. He also released the source code to the AIL/32 protected mode variant of those drivers, which are used in a number of protected mode DOS games, including SimCity 2000.

I figured it might be cool to use the AIL/32 driver model as a basis for writing drivers for more modern sound devices (AC'97, Intel HDA, etc).
However, the existing AIL/32 driver sources are written in assembly language. I managed to figure out how to write a dummy driver that successfully loads as a DLL (as the original AIL/32 did). But I have been unable to figure out how to write the same dummy driver in C. I just can't seem to get it to compile in a way that the entry points end up at the right location. The reason why I want to be able to write AIL/32 in C as well, is because I'm hoping that his would allow me to port over source code from Linux and BSD sound drivers to DOS.

I started out with Wohlstand's "port" of the AIL/32 driver code base that builds with GNU Make, Open Watcom and JWasm.
It was a bit challenging writing a separate driver, since the drivers for the various types of sound cards are compiled from a single assembly file, depending on which flags (defines) are provided on the command-line, as you can see in the Makefile. I started out with the PAS (Pro Audio Spectrum) driver as a basis and first stripped out all the code specific to other sound cards, and lastly the PAS-specific stuff, which got me a dummy or skeleton driver that gets loaded successfully by the stp32.exe example utility, which loads a specified digital audio AIL/32 driver and then plays back a specified .WAV file through it.
It successfully loads my skeleton driver in assembly language (that's basically the PAS driver source with all the device-specific stuff stripped out).

The mechanism for loading the AIL/32 DLL drivers is implemented in the `dllload.c` file.

Now the second step was to try writing and compiling the same dummy driver, but in C, but I can't get stp32.exe to load it.

Are there any people here who are savvy enough both in DOS assembly and C to help me figure out how to get it to work?

I guess I could take the assembly-based dummy file as a basis for a C driver as well, and then include C sources and call those from the assembly routines, but that seems kind of redundant. I'm sure there's a way to write and compile an AIL/32 driver in C directly, right?

I'm attaching the dummy driver code in assembly below:
 
Code:
;█████████████████████████████████████████████████████████████████████████████
;██                                                                         ██
;██  A32DUMDG.ASM                                                           ██
;██                                                                         ██
;██  Digital sound dummy/skeleton driver                                    ██
;██                                                                         ██
;██  Version 0.0.1 of 16-Apr-23: First dummy/stub version (DLL loads)       ██
;██                                                                         ██
;██  80386 ASM source tested with JWASM, should work with MASM 6.0 or later ██
;██                                                                         ██
;█████████████████████████████████████████████████████████████████████████████
;██                                                                         ██
;██  Copyright (C) 1991-1993 Miles Design, Inc.                             ██
;██                                                                         ██
;█████████████████████████████████████████████████████████████████████████████

                OPTION SCOPED           ;Enable local labels
                .386                    ;Enable 386 instruction set
                 .MODEL FLAT,C           ;Flat memory model, C calls

FALSE           equ 0
TRUE            equ -1

                ; Sound driver types, equates for drvr_desc.drvr_type values

XMIDI_DRVR      equ 3                   ; MIDI (music) driver
DSP_DRVR        equ 2                   ; Digital audio driver

                ;
                ;External/configuration equates
                ;

DAC_STOPPED     equ 0
DAC_PAUSED      equ 1
DAC_PLAYING     equ 2
DAC_DONE        equ 3

                ;
                ;Macros, internal equates
                ;

                INCLUDE 386.mac         ;DOS extender macros
                INCLUDE ail32.inc

                ;
                ;Normalize far pointer
                ;(real-mode seg:off)
                ;

FAR_TO_HUGE     MACRO fp_seg,fp_off
                push ax
                push bx
                mov ax,fp_seg
                mov bx,fp_off
                shr bx,1
                shr bx,1
                shr bx,1
                shr bx,1
                add ax,bx
                mov fp_seg,ax
                and fp_off,0fh
                pop bx
                pop ax
                ENDM

                ;
                ;Add 32-bit dword to far ptr
                ;(real-mode seg:off)
                ;

ADD_PTR         MACRO add_l,add_h,pseg,poff
                push bx
                push cx
                mov bx,pseg
                xor cx,cx
                REPT 4
                shl bx,1
                rcl cx,1
                ENDM
                add bx,poff
                adc cx,0
                add bx,add_l
                adc cx,add_h
                mov poff,bx
                and poff,1111b
                REPT 4
                shr cx,1
                rcr bx,1
                ENDM
                mov pseg,bx
                pop cx
                pop bx
                ENDM

STEREO          EQU 1

                .CODE

                ;
                ;Vector table
                ;

                PUBLIC driver_start

driver_start    dd OFFSET driver_index
                db 'Copyright (C) 1991,1992 Miles Design, Inc.',01ah

driver_index    LABEL DWORD
                dd AIL_DESC_DRVR,OFFSET describe_driver
                ; TODO : implement the rest of the AIL API
                dd -1

                ;
                ;Driver Description Table (DDT)
                ;Returned by describe_driver() proc
                ;

DDT             LABEL WORD
min_API_version dd 200                  ;Minimum API version required = 2.00
driver_type     dd DSP_DRVR             ;Type 2: SBlaster DSP emulation
data_suffix     db 'VOC',0              ;Supports .VOC files directly
device_name_o   dd OFFSET devnames      ;Pointer to list of supported devices
default_IO      LABEL WORD              ;Factory default I/O parameters
                dd -1                   ;(determined from the PCI configuration space)
default_IRQ     LABEL WORD
                dd -1                   ;(determined from the PCI configuration space)
default_DMA     LABEL WORD
                dd -1                   ;(N/A: this is a PCI device)
default_DRQ     dd -1
service_rate    dd -1                   ;No periodic service required
display_size    dd 0                    ;No display

devnames        LABEL BYTE
                db "Digital Sound dummy/skeleton driver",0
                ;db "TODO: add line for each supported device/family here',0
                db 0                    ;0 to end list of device names

;****************************************************************************
;*                                                                          *
;*  Public (API-accessible) procedures                                      *
;*                                                                          *
;****************************************************************************

describe_driver PROC USES ebx esi edi

                pushfd                  ;Return CS:near ptr to DDT
                cli

                mov eax,OFFSET DDT

                POP_F
                ret
describe_driver ENDP

;****************************************************************************
                END

By the way, the AIL/32 driver model is implemented in a way that any procedure that is not implemented under the section "Public (API-accessible) procedures" will automatically be stubbed.

The dummy driver can be built like this:
Bash:
jwasm -q -c -W0 -Cp -Zd -Foa32dumdg.o a32dumdg.asm
jwlink option quiet n a32dumdg.dll f a32dumdg.o format os2 lx dll

(Instead of jwlink, you can also use Open Watcom wlink, using the same arguments.)
 
This is what I have tried for the C equivalent so far, which doesn't work:
C:
// Confirmed after testing: long int is a DWORD (32-bit, 4 bytes)

typedef struct
{
    unsigned min_API_version;
    unsigned drvr_type;
    char data_suffix[4];
    char dev_names[32]; // Should be void *dev_name_table;
    int default_IO;
    int default_IRQ;
    int default_DMA;
    int default_DRQ;
    int service_rate;
    unsigned display_size;
} drvr_desc;

const long int AIL_DESC_DRVR;
const drvr_desc DDT;
const drvr_desc * describe_driver();

// PUBLIC driver_start
const long int driver_index_ptr = &AIL_DESC_DRVR;

/** db 'Copyright (C) 1991,1992 Miles Design, Inc.',01ah (string terminated with character 0x1a, not 0) */
const char COPYRIGHT_NOTICE[43] = {
        0x43, 0x6F, 0x70, 0x79, 0x72, 0x69, 0x67, 0x68, 0x74, 0x20, 0x28, 0x43, 0x29, 0x20, 0x31, 0x39,
        0x39, 0x31, 0x2C, 0x31, 0x39, 0x39, 0x32, 0x20, 0x4D, 0x69, 0x6C, 0x65, 0x73, 0x20, 0x44, 0x65,
        0x73, 0x69, 0x67, 0x6E, 0x2C, 0x20, 0x49, 0x6E, 0x63, 0x2E, 0x1A
};

// Start of driver index
const long int AIL_DESC_DRVR = 100;
const long int describe_driver_ptr = describe_driver;
const long int END_OF_DRIVER_INDEX = -1;
// End of driver index

const drvr_desc DDT =  {
        200,
        2,
        "VOC",
        "Some device",
        -1,
        -1,
        -1,
        -1,
        0
};

const drvr_desc * describe_driver() {
    return &DDT;
}

With Open Watcom, it did compile into a .DLL file, but stp32.exe would refuse to load it:
Bash:
wcc386 -mf -s a32skel.c
wlink n a32skel.dll f a32skel.o format os2 lx dll

I appreciate any help with this! Thank you. 🙂
 
The dev_names char array is not a valid substitute for the device_name_o char pointer. The comment indicates what the type it should be so why not just declare it as char *dev_names. The DDT struct initialization can remain the same.

The pointer is maybe 4 bytes and the char array is 32 bytes. This difference in size is going to change the offset of struct members declared after dev_names. Any code that access the C drvr_desc struct as if it was the Asm DDT struct is going to fail on these. The first 4 bytes of the char array "Some" will be interpreted as a pointer and likely generate an access violation.

As Chuck(G) indicated the C structure will need to be packed so that it matches what the assembly code generates.

The DDT variable if being declared twice,
const drvr_desc DDT;
const drvr_desc DDT = { 200, 2, "VOC", "Some device", -1, -1, -1, -1, 0 };
 
Also, when mapping assembly-specified code, I include <stdint.h> and use the explicit types there. Saves a lot of confusion, particularly when moving between 16, 32 and 64 bit platforms.

e.g. uint8_t is equivalent to "unsigned char"
 
Thanks for the useful pointers (no pun intended) so far! I haven't done much programming in C since my college days, and I just learned something new here: padded structs vs packed structs. :)

I'm going to look more closely at these suggestions when I'm back from work this evening.
I might need some help actually applying some of these suggested changes. I'll let you know.

In the meantime, for anybody reading this thread and noticing additional issues in the code, by all means, please share them here!
 
Alright, I explicitly declared the typedef struct as packed, but it ended up not making any difference, because no padding was being applied to that struct in the first place. I verified this by comparing the checksums of the resulting DLL files, and also by adding the -zpw parameter to the Open Watcom C compiler, which makes it emit warnings whenever it applies padding.

I also got rid of the duplicate declaration. But @jxm, the reason why I made those duplicate declarations is because I wanted to place certain things in a certain order, but forward references aren't permitted by the compiler, which is a problem in locations where I have to place a pointer to something further below in the code.
Perhaps I shouldn't be using consts for this? In a hex viewer, I still see some stuff not being placed in the correct order in the resulting DLL file.

Anyway, it's getting late, so I'll just share what I have so far below. Punching holes in this is absolutely welcome! As I said, my C skills could do with some improvement, and I'm eager to learn.

Thanks again, everyone.

C:
// Confirmed after testing: long int is a DWORD (32-bit, 4 bytes)

typedef _Packed struct
{
    unsigned min_API_version;
    unsigned drvr_type;
    char data_suffix[4];
    void *dev_names; // Should be void *dev_name_table;
    int default_IO;
    int default_IRQ;
    int default_DMA;
    int default_DRQ;
    int service_rate;
    unsigned display_size;
} drvr_desc;

const long int AIL_DESC_DRVR;
const drvr_desc * describe_driver();

// PUBLIC driver_start
const long int driver_index_ptr = &AIL_DESC_DRVR;

/** db 'Copyright (C) 1991,1992 Miles Design, Inc.',01ah (string terminated with character 0x1a, not 0) */
const char COPYRIGHT_NOTICE[43] = {
        0x43, 0x6F, 0x70, 0x79, 0x72, 0x69, 0x67, 0x68, 0x74, 0x20, 0x28, 0x43, 0x29, 0x20, 0x31, 0x39,
        0x39, 0x31, 0x2C, 0x31, 0x39, 0x39, 0x32, 0x20, 0x4D, 0x69, 0x6C, 0x65, 0x73, 0x20, 0x44, 0x65,
        0x73, 0x69, 0x67, 0x6E, 0x2C, 0x20, 0x49, 0x6E, 0x63, 0x2E, 0x1A
};

// Start of driver index
const long int AIL_DESC_DRVR = 100;
const long int describe_driver_ptr = describe_driver;
const long int END_OF_DRIVER_INDEX = -1;
// End of driver index

const char *dev_names = "Some device";

const drvr_desc DDT = {
        200,
        2,
        "VOC",
        &dev_names,
        -1,
        -1,
        -1,
        -1,
        0
};

const drvr_desc * describe_driver() {
    return &DDT;
}

This is also yielding the following warnings, by the way:

Code:
a32skel.c(21): Warning! W102: Type mismatch (warning)
a32skel.c(21): Note! N2003: source conversion type is 'long const *'
a32skel.c(21): Note! N2004: target conversion type is 'long'
a32skel.c(32): Warning! W102: Type mismatch (warning)
a32skel.c(32): Note! N2003: source conversion type is 'drvr_desc const *(*)()'
a32skel.c(32): Note! N2004: target conversion type is 'long'
 
typedef _Packed struct

It probably doesn't matter to finding an answer to your problem but I have never seen this appoach to packed structs in Watcom C before. The only method I have used is the #pragma directive:
Code:
#pragma pack(push)
#pragma pack(1)

typedef struct {

}
FOO;

#pragma pack(pop)
The (push) and (pop) saves and restores the current packing/alignment. The pack(1) means 1 byte alignment, e.g. to align on 16bit boundaries use pack(2).
 
Thanks, @pan069. I found about the _Packed keyword in the Watcom documentation. And I tested it by temporarily adding a struct that would normally be padded, and indeed, placing `_Packed` between `typedef` and `struct` indeed had the affect that structs of that defined type would be packed instead of padded. I used the aforementioned -zpw compiler argument to enable padding warnings, and I also compared the checksums of the resulting DLLs to verify this.

Quoting the documentation:
In addition, the _Packed keyword is provided, and if specified before the struct keyword, will force the structure to be packed (no alignment, no gaps) regardless of the setting of the command-line switch or the #pragma controlling the alignment of members.

Source: https://open-watcom.github.io/open-watcom-v2-wikidocs/clr.html

This documentation also mentions the existence of a compiler argument that can control this behavior as well, but refers to the User's Guide for that. I'll look that up later.

So I guess in the end it comes down to whether you want struct packing to be the default behavior throughout your entire source code (or a certain part of it). In that case, going for the #pragma approach would indeed make the most sense, whereas the _Packed keyword would be preferable when you only want to apply this selectively, or if you prefer this to be explicitly declared everywhere for clarity. The downside of that, however, is that the _Packed keyword is not standardized in the C and C++ spec and different compilers use different mutually incompatible keywords and modifiers for this.

I think my main struggle at this point is how to force certain data structures to be placed in the correct order in the resulting binary, even if some of them contain forward references. Do I need to stop using constants everywhere?

Further help is still welcome! :) (Just keep nudging me in the right direction. I'm not expecting others to solve this completely for me. Again, I'm here to learn.)
 
Hmmm... One thing I'm running into is that I can't declare forward function pointers, because C compilers are single-pass.
For the first forward pointer, which is at the beginning at offset 0, I could calculate this, since it has to point to the memory address just after the copyright message, which has a fixed length.

But then there is the "driver index", which associates each implemented "dynamically linked driver procedure number" with its corresponding function offset.

So I'm talking about this snippet:

Code:
                PUBLIC driver_start

driver_start    dd OFFSET driver_index
                db 'Copyright (C) 1991,1992 Miles Design, Inc.',01ah

driver_index    LABEL DWORD
                dd AIL_DESC_DRVR,OFFSET describe_driver
                ; TODO : implement the rest of the AIL API
                dd -1
               
; ...

describe_driver PROC USES ebx esi edi
; (function implementation here)

How could I implement this in C? Particularly the driver_index part, with function pointers pointing towards the implemented procedures lower in the file.

Also, even implementing the basis of this, with an empty driving index, in C, the resulting object file, when linked to an AIL/32 driver DLL file, still does not allow the copyright string to be detected by the AIL/32 driver loader. Comparing the object files of the assembly output with that of the C output shows that there are a lot more differences between the files that I can't figure out, possibly debugging symbols?

Would it perhaps be a better approach to keep the assembly source file that contains the expected entrypoint structure (copyright message and driver index) and then delegates to C code in other files from within the implemented procedures?

Any help on this is still very welcome! Thanks.
 
You can forward-declare functions in C:
int func_a(int, long);
long func_b(const char *, int);
Then you can use their addresses, even if the actual implementation is at the bottom. Such declarations do not generate code.

However, function address tables called from assembly code in C sounds... a bit scary. You have to follow a set of rules (ABI, see https://en.wikipedia.org/wiki/X86_calling_conventions#List_of_x86_calling_conventions) before jumping into a C function. Unless the caller uses a calling convention supported by your compiler, you may need to provide wrapper code written in assembly to transition between assembly and C code. Watcom is very flexible, but this is non-standard compiler magic, so check its documentation.

Personally, I always use "unsigned int" and "long" (never "unsigned" and "long int"), and this is true for most code I have looked at as well.

How could I implement this in C? Particularly the driver_index part, with function pointers pointing towards the implemented procedures lower in the file.
If your binary needs to start with a specific header, you should have a very long and deep discussion with your linker first. The code and data layout in your binary file is not standardized in C at all, and most toolchains are likely going to put runtime startup code at the beginning - following the PE (or ELF or NE) headers, which make your binary a valid executable/library. There is no guarantee that stuff is not reordered, either.

Needless to say, all of this is toolchain-specific.

Would it perhaps be a better approach to keep the assembly source file that contains the expected entrypoint structure (copyright message and driver index) and then delegates to C code in other files from within the implemented procedures?
Most likely yes, because that would allow your assembly stubs to adapt the ABI if necessary.
 
Thank you for your useful advice, @Svenska.

To give everybody here a bit more context:

The AIL/32 drivers are a protected mode variant of the AIL (version 2) drivers developed by John Miles in the '90s. These drivers are meant to be loadable modules for use in a 32-bit protected mode environment, specifically in DOS games that use DOS extenders.

Some years ago, John Miles released the AIL drivers as open-source, including the AIL/32 drivers. They should not be confused with the Miles Sound System (version 3) drivers, which succeeded those drivers and were also used in a number of protected mode DOS games, as well as in Windows games. Those were never open-sourced, and they were later sold to Rad Game Tools.

Anyway, back to the now open-source AIL/32 (version 2) drivers:

As you also pointed out w.r.t. specific headers and "having a deep discussion with your linker", my guess is that John Miles didn't want to come up with a new loadable module format that would require support in popular linkers at the time, such as that of Microsoft, Watcom, Borland, etc. So instead, he settled on an extisting common 32-bit binary file format that would fit the bill, and he settled on the 32-bit Linear Executable OS/2 DLL format. Not that this driver model had anything to do with OS/2, it just fit his needs and was a binary module format that was commonly supported in popular toolchains and allowed the building of 32-bit driver code that could be loaded by games and other applications dynamically.

On the application side of things, the AIL/32 SDK contains tools to allow game and application developers to add support for these drivers, specifically the ability to load the drivers, and access them through the AIL/32 API, from either assembly language or C code. One part of the AIL/32 SDK is the DLL loader source, in `dll.h` and `dllload.c`. That's a custom DLL loader that looks for the Copyright string at the top of the DLL file, to check whether the DLL being loaded is an actual AIL/32 DLL, and not just any OS/2 LX DLL file.

So long story short: it appears that John Miles repurposed the OS/2 LX DLL binary format to allow AIL/32 drivers to be built by common assemblers and linkers, while at the same time offering a custom loader with the SDK, to distinguish AIL/32 driver DLLs from any other DLLs.

Back when the drivers were still closed-source and commercially offered, the SDK allowed for developers to access the API from both assembly language and C code. But once John Miles open-sourced both the SDK and the drivers themselves, it became clear that the drivers themselves were written in assembly, with no examples on how to implement such a driver in C.

So why am I trying to write an AIL/32 driver in C? Well, because most available open-source sound driver sources, typically for POSIX operating systems such as Linux and BSD-variants, are all written in C, and I'd like to "port" those to DOS using a driver format that is open-source, has support for 32-bit protected mode and is actually in use by a number of games.

A secondary step would be adapting the recently developed Sound Blaster emulator SBEMU by crazii, and/or its VSBHDA fork by Baron von Riedesel (a.k.a. Japheth), to support pluggable hardware backends. The AIL/32 driver model seems like a viable candidate for that.

For reference, here is a "fork" of AIL/32 by GitHub user Wohlstand, who figured out how to build the AIL/32 drivers and the SDK using a modern open-source toolchain (GNU Make and Open Watcom). I've been using that fork as the basis for my work.
 
Last edited:
By the way, It's not easy finding on-line examples on how to easily call C functions from assembly code. Most of the examples I find on-line tell you how to do the opposite. I know that calling conventions need to be taken into account, and it appears to differ per assembly dialect and toolchain. I'm specifically looking for examples on how to do this with an (Open) Watcom toolchain. I'm learning as I go, but any help in learning how to do this would be very much appreciated. Thanks! 🙂
 
If the system uses a proper linked DLL format (without a magic header), then your copyright strings and tables can be located anywhere in the file (located by their symbol name) and you don't actually need to care. I haven't looked at the linked source code, so I don't know how things actually work apart from your descriptions.

When it comes interfacing C with assembly, I usually just compile a short test function with my target toolchain and look at the compiler-generated assembly output ("cc -S" or "wdis" on the object file). The result is guaranteed to match the expected calling convention. Local variables and the associated entry/exit code can be tricky to get right.
 
well if your using watcom you need to know it has a dozen calling conventions and it depends on who is calling whom...

-ec{c,d,f,p,r,s,w}
c - __cdecl
d - __stdcall
f - __fastcall
p - __pascal
r - __fortran
s - __syscall
w - __watcall (default)


you need to know what the game/app? thats loading the ail/2 drivers is using and use that... watcom will define a lead or trailing _ on names depending on the calling convention. so if you write nasm, you do like a function named "myfunc()" call in C, would be asm

;; catch the call
_myfunc:
myfunc:
myfunc_:


obviously you cant mix watcom calling convention with cdecl or pascal because everyone passes arguments different and expects different register/stack return conventions and how it removes arguments from the stack (or not!)...

once you know which one, then it becomes really easy.
 
Last edited:
Good one! Another puzzle piece. I didn't know that Watcom defaulted to its own proprietary calling convention.
The C source and header files of the AIL/32 SDK and driver sources have the `cdecl` keyword in all function declarations and definitions. This includes `ail32.h`, which is the driver API. So I guess that answers that question. I guess it makes sense to standardize on the "C declaration" calling convention for this driver model, since 32-bit DOS protected mode applications and games that would make use of these drivers were typically written in C or C++.

I'll adjust my code to include the `cdecl` keyword in all the function declarations. Thanks for that!

However, I don't think that explains why the driver loading code isn't at least detecting the copyright string at the top of the file, past the first 32 bits that contain the pointer to the driver index.

As for catching a C call in assembly: if I'm going to keep an assembly shim and defer to C code from there, I'll need to do the opposite: call C functions from assembly code. Can anybody maybe share an example of how to do that, assuming the C calling convention (`cdecl`)? Thanks again!
 
However, I don't think that explains why the driver loading code isn't at least detecting the copyright string at the top of the file, past the first 32 bits that contain the pointer to the driver index.

i would guess, you need to shim the message as code (.text) and not a string (aka .data). because when you link it, it will link text/code, data, then bss. and if you have strings, they are data. watcom can compile strings into code, but it probably wont be positioned as you want. you need to create say an asm shim that decalres .text and link that obj first before the c objs with wlink. (or a pragma aux string or something).

(writing the above without seeing the actual binary output.. so making a guess) unless I'm totally wrong and your not doing it all in c, if its in nasm them it should be fairly simple to set things where you want.
 
ok heres an example of doing the cdecl calls from c<>asm<>c.

C calls into our assembler, which calls printf back in C land. below is a build.bat file, x.asm and q.c that i knocked out


--build.bat--
nasm x.asm -fobj -o x.obj wcc386 -oneatx -ecc -3 q.c wlink name test.exe debug all option {map=xx.map} file {x q} system pmodew

--x.asm--
[bits 32] [cpu 386] global _myfunc extern printf_ [section .text class=CODE align=16 USE32] group dgroup data bss _myfunc: push ebp mov ebp,esp push eax ;; push in reverse order mov eax,0xDEADBEEF push eax mov eax,mystring push eax call printf_ ;; drop parameters add esp,8 ;; 2 parameters pop eax ;; not entirely necessary but covers any left over stack junk mov esp,ebp pop ebp ret [section .data class=DATA align=16 USE32] mystring: db "whoa C->NASM->C! 0x%08lX",10,0 [section .bss class=BSS align=16 USE32] _bss_starts:


-- q.c --
#include <stdlib.h> #include <stdio.h> extern void __cdecl myfunc(void); int main(int argc, char *argv[]) { myfunc(); return 0; }
 
Back
Top