使用 GNU Assembly 使扬声器在 Ubuntu Linux 中以定义的频率振动

Make a speaker vibrate at a defined frequency in Ubuntu Linux, with GNU Assembly

提问人:STC2 提问时间:10/27/2023 更新时间:11/5/2023 访问量:61

问:

我是汇编语言的新手

我们的计算机一直在发出声音(mp3、wav、mp4、ogg 等)。

如何使用汇编语言直接与说话者交流?在 C 语言中,您可以使用“Beep()”并将持续时间(以毫秒和赫兹为单位)传递给扬声器。我怎样才能用GNU汇编语言做到这一点?

我的电脑规格: 戴尔 Optiplex 960; Ubuntu 23.04 LTS x86-64 体系结构。

有人可以留下并举例说明如何做到这一点,即粘贴一些源代码吗? 对每个指令正在做的事情发表评论也将非常有帮助。

我试图找到一些教程,但我有点迷茫。大多数在 NASM(不是我使用的 GNU)中的教程都会使用系统默认的声音,即 Windows 上的铃声或 Linux Ubuntu 上的通知声音。

linux 汇编 gnu gnu-assembler 低级

评论

2赞 Peter Cordes 10/27/2023
您是否尝试在与 PC 兼容的设备上使用 PC 扬声器(通常不与现代台式机中的任何东西相连)。或者通过声卡(例如通过 ALSA)的常规扬声器,自己为正弦波生成 PCM 样本?
1赞 Peter Cordes 10/27/2023
我的 Arch GNU/Linux 系统没有功能。 仅显示仅输出终端铃铛字符的 ncurses(小写),没有频率参数。当然没有 ISO C 标准或函数,所以我不知道你在说什么 Beep() 函数。Beep()man -a Beepvoid beep(void)Beep(int)double
1赞 David C. Rankin 10/27/2023
如果你有一个扬声器,而不是你可以与之交谈(你经常需要单独安装它),那么系统调用中的信息在linux下产生声音?(最好的讨论在底部)应该为你指明正确的方向。nasm 列表(缺少一些标签),否则包含到达硬件扬声器所需的内容。要测试短促的哔哔声,您可以使用已安装它(并具有硬件扬声器)beep':'beep -f 5000 -l 50 -r 2
1赞 Peter Cordes 10/27/2023
@DavidC.Rankin:如果其中有任何缓冲区溢出或其他漏洞,则永远的setuid-root二进制文件可能比手动的漏洞更严重。(由汇编新手编写的学习练习并不是强化软件的秘诀;如果你没有在每个ASM源文件中包含一个特殊的部分,你的过程甚至会得到一个可执行的堆栈,使传统的代码注入攻击成为可能)。这里的一个优点是,输入希望只是一个数字,但如果你对它做任何事情,要小心整数溢出......sudo.note
3赞 Margaret Bloom 10/27/2023
您可以使用 libevdev 或直接使用 evdev 接口(请参阅此处)。您还可以设置 ioperm 并直接访问端口 0x61 和 0x43/42(请参阅此处)。对于后者,您可能需要阅读有关 PIT、其基本频率和 8042 控制器的信息,才能真正了解您在做什么。请注意,除非您出于某种原因想要使用 IO 端口,否则此任务不需要组装。

答:

3赞 ndim 11/4/2023 #1

更新:根据戴尔 Optiplex 960 的在线服务手册,内置扬声器是可选设备。因此,您的计算机可能没有连接内部扬声器,对问题的评论以及有关使用 PC 扬声器界面的答案将无济于事。这个答案主要解释了使用内置扬声器,但最后对 PCM 音频有几点评论。

由于几年来我一直在维护通用的 Linux 哔哔声实用程序 https://github.com/spkr-beep/beep,因此我建议在 Linux 上为 PC 扬声器使用正确的 API,而无需特权访问。

这相当于一个程序

  • open(2)已知设备文件并保存文件描述符 如果成功,否则中止程序/dev/input/by-path/platform-pcspkr-event-spkr
  • 准备一个(定义在)中,(频率将使PC扬声器静音)(可以静态完成)struct input_eventlinux/input.he.type = EV_SNDe.code = SND_TONEe.value = frequency_in_Hz0
  • write(2)该文件描述符的 24 字节结构
  • 最终文件描述符close(2)

所以基本上,你只需要编写一个程序,将几个字节写入一个文件。

如果希望程序开始提示音,然后等待,然后停止该提示音,则程序还需要等待定义的时间。带有 from 的系统调用可以为您提供帮助。nanosleep(2)struct timespectime.h

这样一来,您只需要弄清楚如何执行系统调用,然后。好吧,如果你之后无论如何都不需要,但可能也需要。open(2)write(2)close(2)close(2)exit(2)nanosleep(2)

汇编中的任何 Linux“hello world”程序都应该是一个很好的起点:它已经实现了 和 ,这应该是添加其他系统调用时的好起点。write(2)exit(2)

从一个简单的hello world程序开始,如 https://cs.lmu.edu/~ray/notes/gasexamples/(在搜索引擎中首次点击“linux gnu assembly hello world”),从 https://blog.rchapman.org/posts/Linux_System_Call_Table_for_x86_64/(在搜索引擎中首次点击“x86-64 abi”)的一些信息开始,加上一些我自己的装饰(、、、、和调试),给出以下程序,它发出哔哔声。停止哔哔声留给读者作为练习,或用于在外壳上运行。.sizefoo_size = . - foo.balign.globalbeep

# gas-beep.s - do a PC speaker beep with GNU assembler for Linux x86-64

    .global _start
    .text
    .global main_program
main_program:
_start:
    # open(speaker_device, O_WRONLY)
    mov $2, %rax        # sys_open
    mov $speaker_device, %rdi   # filename = speaker_device
    mov $1, %rsi        # flags = O_WRONLY
    xor %rdx, %rdx      # mode = 0
    syscall

    cmp $0, %rax
    jl  error_exit_open

    mov %rax, fd

    # write(fd, &beepcmd_on, sizeof(beepcmd_on))
    mov $1, %rax        # sys_write
    mov fd, %rdi        # fd = fd
    mov $beepcmd_on, %rsi   # buf = beepcmd_on
    mov $beepcmd_on_size, %rdx  # count = sizeof(beepcmd_on)
    syscall

# TODO: nanosleep(2) with a struct timeval aka two .quad values
# TODO: turn off sound using write(2) (beepcmd_off with frequency 0)
# TODO: print a few enlightening words to stdout with write(2)

    # close(fd)
    mov $3, %rax        # sys_close
    mov fd, %rdi        # fd = fd
    syscall

    # exit(0)
    mov $60, %rax       # sys_exit
    xor %rdi, %rdi      # error_code = 0
    syscall

error_exit_open:
    # write(1, errmsg_open, sizeof(errmsg_open))
    mov $1, %rax        # sys_write
    mov $2, %rdi        # fd = STDERR_FILENO
    mov $errmsg_open, %rsi  # buf = errmsg_open
    mov $errmsg_open_size, %rdx # count = sizeof(errmsg_open)
    syscall

    # exit(2)
    mov $60, %rax       # sys_exit
    xor $2, %rdi        # error_code = 2
    syscall

    .size   main_program, . - main_program

    .balign 8
    .global beepcmd_on
    # struct input_event {.type=EV_SND, .code=SND_TONE, .value=880}
beepcmd_on:
    .quad   0
    .quad   0
    .short  0x12            # .type = EV_SND
    .short  0x02            # .code = SND_TONE
    .int    880         # .value = frequency
beepcmd_on_size = . - beepcmd_on    # required for write(2)
    .size   beepcmd_on, beepcmd_on_size

    .global speaker_device
speaker_device:
    .asciz  "/dev/input/by-path/platform-pcspkr-event-spkr"
    .size   speaker_device, . - speaker_device

    .global errmsg_open
errmsg_open:
    .ascii  "Error: Could not open(2) the device.\n"
errmsg_open_size = . - errmsg_open  # required for write(2)
    .size   errmsg_open, errmsg_open_size

    .bss

    .global fd
    .lcomm  fd, 8           # uninitialized file descriptor variable

为了使构建和理解它更容易,请使用以下命令来构建整个程序的符号列表、地图文件、反汇编视图等:GNUmakefile

.PHONY: all
all: all-local

TARGETS += gas-beep.lss
%.lss: %
    objdump -h -S $< > $@

TARGETS += gas-beep.syms
%.syms: %
    objdump --syms $< > $@

TARGETS += gas-beep.nm
%.nm: %
    nm $< > $@

TARGETS += gas-beep.map
%.map: %

TARGETS += gas-beep
gas-beep: gas-beep.o
    ld -g -Map=gas-beep.map -o $@ $^

TARGETS += gas-beep.lst
%.lst: %.o

TARGETS += gas-beep.stripped
%.stripped: %
    strip -s -o $@ $<

TARGETS += gas-beep.readelf-a
%.readelf-a: %
    readelf -a $< > $@

%.o: %.s
    gcc -Wall -Werror -g -Wa,-adhlns=$*.lst -c -o $@ $<

.PHONY: all-local
all-local: $(TARGETS)

.PHONY: clean
clean:
    rm -f *.{lss,lst,map,nm,o,readelf-a,stripped,syms} gas-beep

请注意,您可能需要显式安装并加载内核模块,并确保您的计算机实际上连接了物理 PC 扬声器(扬声器或压电蜂鸣器)。安装操作系统的软件包可以为您节省一些手动系统管理员工作。pcspkr.kobeep

PC 扬声器就这么多。

如果您想通过 PCM 音频而不是使用 PC 扬声器来产生声音,那么 API 可能比仅仅向文件写入几个字节要复杂得多。几年前,为PCM音频准备一个简单的内存缓冲区,用方波连接到一个PCM音频设备,其复杂性相当。然而,如今,即使只是找到一个要打开的设备也要复杂得多,我们甚至不是在谈论缓冲区用完和重新填充缓冲区,而且有不止一个 PCM 声音 API。write(2)open(2)/dev/dsp