2

I'm currently trying to generate a sound in assembly code. Here is some code i found:

    section .text
  global sound

sound:
    mov     al, 182         ; meaning that we're about to load
    mov     ax, 182
    out     43h, al         ; a new countdown value
    ret
    mov     ax, 2153        ; countdown value is stored in ax. It is calculated by 
    out     42h, al         ; Output low byte.
    mov     al, ah          ; Output high byte.
    out     42h, al               

    in      al, 61h         
    or      al, 00000011b  
    out     61h, al     ; Send the new value
    ret

According to the owner of this code, it should be working, but when I run it with a C main, it's killed by a segmentation fault.

With further researches, I found that in modern OS, it was harder to access the speakers because of rights . If someone knows how to access the speakers and play a sound, I would love to learn it.

Note: when I run my code as a super user, I get no segmentation fault, but no sound is produced.

Zoe
  • 27,060
  • 21
  • 118
  • 148
bachinblack
  • 150
  • 12
  • 2
    Find a library (e.g. FMOD, BASS) that works on the operating system(s) you want to target, then use that library in your program. – Michael Mar 24 '17 at 13:21
  • 1
    The `out` instruction is privileged. Also it targets hardware that no longer exists. You should either run this in DOSBox or get a library that does sound under Linux. – Johan Mar 24 '17 at 13:45
  • So there are sound libraries in asm? I'm sorry, I'm a newbie with assembly code. I'll search for something like this, Thanks to the two of you for your answers – bachinblack Mar 24 '17 at 15:25
  • 1
    Running as root allows a Linux [`ioperm`](http://man7.org/linux/man-pages/man2/ioperm.2.html) system call to succeed, but this will still segfault for root if you didn't do that. Maybe the way you're running as root hid the segfault message? Superuser is still separate from kernel mode, although ioperm / iopl can allow I/O instructions (which are normally privileged) to be executed in user-space. – Peter Cordes Dec 29 '19 at 09:10

1 Answers1

4

[BEFORE YOU READ THIS WORKS WITH THE NASM COMPILER, I'M NOT SURE ABOUT OTHER COMPILERS]

Your code is fine but here's the problem:

  1. It will not produce sound in a virtual machine. What the 0x43, 0x42 ports are for are the PIT (Programmable Interval Timer) chip which handles somethings which you can read up on here OSDEVWIKI. One of the things the PIT chip handles is the on board speaker which is what you are trying to access. Virtual machines don't have an onboardspeaker.

Long story short you are trying to access the motherboard speaker but virtual machines can't use it so you don't hear anything.

Also most newer motherboards don't have on board speakers either.

  1. Your code. So from just a glance at your code I'm guessing you're using nasm so I'm a little confused about this:
mov     al, 182         ; meaning that we're about to load
mov     ax, 182
out     43h, al         ; a new countdown value
ret

To touch up this part a little you can remove the mov ax, 182 and the ret because the return right there will jump out of the code to where you called it no longer running the code making:

mov     ax, 2153        ; countdown value is stored in ax. It is calculated by 
out     42h, al         ; Output low byte.
mov     al, ah          ; Output high byte.
out     42h, al               

in      al, 61h         
or      al, 00000011b  
out     61h, al     ; Send the new value
ret

obsolete. For the mov ax, 182 when I use the speaker mine works without this so my guess is you don't need it so I'd say remove it if it is necessary.

So a revamped version of your code would be:

bits 16

start:
mov ax, 0x07c0   ; Setup the stack past where we are loaded
add ax, 544
cli              ; Disable interrupts
mov ss, ax
mov sp, 4096
sti              ; Restore interrupts

mov ax, 0x07c0   ; Set the data segment to where we are
mov ds, ax

sound:
mov al, 182         ; Were about to load
out 0x43, al

mov ax, 15000       ; 15000(Pitch) = 1,193,180 / 79.5453333(Repeating)

out 0x42, al        ; Give the port the lower value
mov al, ah
out 0x42, al        ; Now give it the higher

in al, 0x61         

or al, 00000011b    ; Connect the speaker to Timer 2
out 0x61, al
jmp $               ; hang

times 510-($-$$) db 0     ; Pad the rest of the file with 0's
dw 0xaa55                 ; Little Endian MBR Signature

To run this to see if this works make sure the computer you have has an on board speaker. Mine looks like this: here

If you don't have it yours may not look like mine, if there isn't one you could display or make something happen after the code to make sure it ran.

I use these commands to run it.

nasm -f bin <YOURFILENAME>.asm -o boot.bin
dd if=boot.bin of=\\.\<DRIVENUMBER>: bs=512

What these do is the nasm one is the nasm compiler and compiles the assembly file. The dd one is rawwrite if you are on linux it is already there, but on windows like I am using you can download rawwrite here. MAKE SURE YOUR DRIVE NUMBER IS THE DRIVE YOU ARE USING SUCH AS A USB OR FLOPPY. An example would be if your usb is on drive d: you would use: dd if=boot.bin of=\\.\d: bs=512

Then you can plug the usb into the computer with the oscillator. Boot up the computer and get into the boot menu for me I just spam f10 until I get there but for you it may be a different button there you will be asked what device you should boot to find your usb and boot to it. It should boot if not have the os display something like:

mov ah, 0x0e
mov al, 'X'
int 0x10

To see if it is booting if not add this to the top of the file AFTER bits 16:

jmp short start
nop

OEMLabel            db "Contoso"   ; OEM Label
BytesPerSector      dw 512          ; There are 512b per sector
SectorsPerCluster   db 1            ; Sectors per cluster
ReservedForBoot     dw 1            ; # of sectors reserved for boot
NumberOfFats        db 2            ; # of fats
RootDirEntries      dw 224          ; # of root directory entries
LogicalSectors      dw 2880         ; # of sectors
MediumByte          db 0x0f0        ; Medium descriptor byte
SectorsPerFat       dw 9            ; # of sectors per fat
SectorsPerTrack     dw 18           ; # of sectors per track
Sides               dw 2            ; # of sides
HiddenSectors       dd 0            ; # of hidden sectors
LargeSectors        dd 0            ; # of large sectors
DriveNo             dw 0            ; The drive number
Signature           db 41           ; The drive signature
VolumeID            dd 0xdeadbeef   ; The volume id
VolumeLabel         db "Windows9   "; This can be any 11 characters
FileSystem          db "FAT12   "   ; File system of the floppy DONT CHANGE

If for any reason the code doesn't work check for any errors and make sure your motherboard has an on board speaker.

For more info go here as said above. Base of the source code here. For some tips on assembly here, this is a very well documented open source OS to help beginners learn assembly.

Hope this helps!

EDIT: Some Typos and help from Peter Cordes (In the comments)

  • 1
    A virtual machine that accurately emulates a legacy IBM-PC will have that virtual hardware. e.g. probably BOCHS or DOSBox. When you say "oscillator", I think you actually mean "PC Speaker". In your photo it's a piezo on the mobo; in other cases it can be a header on the board that you're expected to connect to a coil speaker. If you have an old PC case, you can get the speaker out of it and put that inside a modern case to hook up to the mobo. Or of course you can run in an emulator that uses regular sound I/O function to produce the beeps via the host's sound card. – Peter Cordes Dec 29 '19 at 08:27
  • 1
    BTW, I think you left out the `0xAA55` MBR boot signature at the end of your 512-byte boot sector (e.g. [like in this example](https://stackoverflow.com/questions/36052492/times-510-db-0-does-not-work)). Possibly your test worked because you wrote fewer than 512 bytes so the existing signature was preserved? Or else your BIOS doesn't check for that signature before loading a boot sector. – Peter Cordes Dec 29 '19 at 08:35
  • Yes I did thanks for pointing these out for me m8. I'm gonna fix it really quick. It always helps. – Bored Coder Dec 29 '19 at 08:39
  • 1
    BTW, the problem in the question is that they're trying to run this code in user-space under Linux or MacOS!! (The phrase "segmentation fault" is a clue there). That's a showstopper, separate from the bugs like an early `ret` (probably added to see if just the first part still segfaulted). It might possibly work if you made an `ioperm` system call to enable those I/O ports for use from user-space. Or yes, of course writing a bootloader gives you real mode control of the whole machine. – Peter Cordes Dec 29 '19 at 08:43
  • Oh, ok, yea I use qemu x86 on windows and when I run that code in that or virtual box it doesn't make a sound, but when I run on a legit system it works fine from the on board speaker. So I think they could use the ioperm or try to use the actual speakers. I'll see if I can get the real speakers to work on Virtual Box or qemu than edit the answer with that solution. – Bored Coder Dec 29 '19 at 08:53
  • I'm pretty sure DOSBox would emulate PC-speaker beeps. I'd guess that BOCHS would too. They both care about faithful enough emulation to run legacy software (with DOSBox caring a lot about games, so very likely to support it). https://wiki.osdev.org/PC_Speaker#Sample_Code says QEMU supports a `-soundhw pcspk` option you can use to enable emulating that HW. Presumably you forgot that option. (Or your qemu is too old; other google results show that support didn't exist back in 2009 but IDK when it was added.) – Peter Cordes Dec 29 '19 at 08:59
  • 1
    Ok well the `-soundhw pcspk` worked on qemu so I guess maybe his problem was one of those settings. Or it was a bug in his code. – Bored Coder Dec 29 '19 at 09:03
  • 1
    Uh yeah, the bug was naively running this code in user-space under Linux, without using `ioperm` or anything, and being surprised that `out` segfaulted. :P Many beginners don't realize how non-portable assembly language is, and wonder why `int 21h` doesn't work in a Windows (not DOS) executable and stuff like that after copy/pasting some code they found for e.g. printing a number "in x86 assembly" – Peter Cordes Dec 29 '19 at 09:08
  • 2
    OEMLabel should be 8 bytes long, not 7. – ecm Dec 29 '19 at 11:01