Skip to main content
Redhat Developers  Logo
  • AI

    Get started with AI

    • Red Hat AI
      Accelerate the development and deployment of enterprise AI solutions.
    • AI learning hub
      Explore learning materials and tools, organized by task.
    • AI interactive demos
      Click through scenarios with Red Hat AI, including training LLMs and more.
    • AI/ML learning paths
      Expand your OpenShift AI knowledge using these learning resources.
    • AI quickstarts
      Focused AI use cases designed for fast deployment on Red Hat AI platforms.
    • No-cost AI training
      Foundational Red Hat AI training.

    Featured resources

    • OpenShift AI learning
    • Open source AI for developers
    • AI product application development
    • Open source-powered AI/ML for hybrid cloud
    • AI and Node.js cheat sheet

    Red Hat AI Factory with NVIDIA

    • Red Hat AI Factory with NVIDIA is a co-engineered, enterprise-grade AI solution for building, deploying, and managing AI at scale across hybrid cloud environments.
    • Explore the solution
  • Learn

    Self-guided

    • Documentation
      Find answers, get step-by-step guidance, and learn how to use Red Hat products.
    • Learning paths
      Explore curated walkthroughs for common development tasks.
    • Guided learning
      Receive custom learning paths powered by our AI assistant.
    • See all learning

    Hands-on

    • Developer Sandbox
      Spin up Red Hat's products and technologies without setup or configuration.
    • Interactive labs
      Learn by doing in these hands-on, browser-based experiences.
    • Interactive demos
      Click through product features in these guided tours.

    Browse by topic

    • AI/ML
    • Automation
    • Java
    • Kubernetes
    • Linux
    • See all topics

    Training & certifications

    • Courses and exams
    • Certifications
    • Skills assessments
    • Red Hat Academy
    • Learning subscription
    • Explore training
  • Build

    Get started

    • Red Hat build of Podman Desktop
      A downloadable, local development hub to experiment with our products and builds.
    • Developer Sandbox
      Spin up Red Hat's products and technologies without setup or configuration.

    Download products

    • Access product downloads to start building and testing right away.
    • Red Hat Enterprise Linux
    • Red Hat AI
    • Red Hat OpenShift
    • Red Hat Ansible Automation Platform
    • See all products

    Featured

    • Red Hat build of OpenJDK
    • Red Hat JBoss Enterprise Application Platform
    • Red Hat OpenShift Dev Spaces
    • Red Hat Developer Toolset

    References

    • E-books
    • Documentation
    • Cheat sheets
    • Architecture center
  • Community

    Get involved

    • Events
    • Live AI events
    • Red Hat Summit
    • Red Hat Accelerators
    • Community discussions

    Follow along

    • Articles & blogs
    • Developer newsletter
    • Videos
    • Github

    Get help

    • Customer service
    • Customer support
    • Regional contacts
    • Find a partner

    Join the Red Hat Developer program

    • Download Red Hat products and project builds, access support documentation, learning content, and more.
    • Explore the benefits

Tips for writing portable assembler with GNU Assembler (GAS)

February 26, 2021
Nick Clifton
Related topics:
LinuxOpen source
Related products:
Developer Toolset

    Writing assembly code is straightforward when you are familiar with the targeted architecture's instruction set, but what if you need to write the code for more than one architecture? For example, you might want to test whether a particular assembler feature is available, or generate an object file for use with another tool. Writing assembly source code that can work on multiple architectures is not so simple.

    This article describes common types of problems encountered when working with assembly code, and the techniques to overcome them. You will learn how to address problems with comments, data, symbols, instructions, and sections in assembly code. To get you started, the Portable assembler demo source file provides many examples of GNU Assembler (GAS) assembly code. I'll use a few of the examples in this article.

    Problems with comments

    There is no architecture-neutral way of creating a prefixed line comment. As a result,

      # This is a comment
    

    might or might not work, depending on the target. (On some architectures the hash character is actually part of the instruction set, similarly for the semicolon and colon characters.)

    Instead, the safe approach is to use C-like comments:

      /* This is a comment. */
    

    But keep in mind that these comments cannot be nested:

      /* This is /* not a */ valid comment. */
    

    Problems with data

    The size of individual data items, such as integers, pointers, floats, and so on, varies from one architecture to another. Take the following example:

      .data
      .word 0x12345678
    

    This code would fail to assemble on machines where a word was less than 4 bytes long. (Fortunately, the .data directive is universal.)

    A more reliable way to insert specific integer values is to use the .dc.<letter> directives, where <letter> is b for bytes, w for 16-bit values, and l for 32-bit values. Here's an example:

      .data
      .dc.b 0x78
      .dc.w 0x5678
      .dc.l 0x12345678
    

    This assembly code works on all targets, regardless of their word size.

    Inserting 64-bit integer values

    Oddly, the directive for 64-bit values does not follow the same naming scheme. Instead the directive to use is .quad:

      .quad 0x1234567890abcdef

    Endian-ness

    All values are stored in the target's endian format, which is usually the right approach. However, when fixed ordering is required, specifying multiple single-byte values is the way to go:

      .data
      .dc.b 0x78, 0x56, 0x34, 0x12
    

    This code produces a little-endian ordering of bytes, even on a big-endian architecture. You cannot however create multi-byte bit patterns on targets where the byte size is larger than 8 bits (for example, the Texas Instrument's TIC54x.) Outside assistance is the only way to handle this particular situation:

      .data
      .ifdef big_bytes
      .dc.b 0x5678, 0x1234
      .else
      .dc.b 0x78, 0x56, 0x34, 0x12
      .endif
    

    This solution works provided the symbol, big_bytes, is defined for architectures with 16-bit bytes and not otherwise. (Symbols can be defined on the GAS command line with --defsym <name>=<value>.)

    Alignment requirements

    Another problem with directives that store data values is that they can have alignment requirements. For example:

      .data
      .dc.b 0xff
      .dc.l 0x12345678
    

    This example fails to assemble for the SH target because the 4 bytes in 0x12345678 are not being stored on a 4-byte aligned boundary. You can solve this issue with an alignment directive, but be cautious of using .align, which has target-specific semantics. Instead, use either the .balign or .p2align directives:

    .data
      .dc.b 0xff
      .balign 4
      .dc.l 0x12345678
    

    Note that this code introduces a gap between the 0xff byte and the 0x12345678 word.

    Fixed values

    GAS supports simple arithmetic and logical operations on symbols and constants. For most directives, the result must be a fixed value. Here's an example:

      .dc.b (val & 0xff), (val >> 8) & 0xff
    

    This code works provided that the symbol val has a defined value when the directive is evaluated.

    Storing strings

    Strings can be stored easily, but beware that the .ascii directive does not store a terminating NUL byte. For C like strings use the .asciz directive instead:

      .ascii "this string has no NUL byte at the end"
      .asciz "this string does"
    

    Problems with symbols

    Labels and symbols are defined in various ways, all of which work across most targets:

      val = 0x1234
      .equiv here, .
      .equiv there, here + 4
      this_is_a_label:
    

    For comparability with the HPPA assembler however, it is necessary to start a label's name in the first column of a line. Plus, by extension, the first column on any line needs to contain a whitespace character if no label is being defined.

    If a symbol or label holds an address, then it is safest to insert it into the code using the .dc.a directive, like so:

      .dc.a this_is_a_label
    

    You can perform simple addition or subtraction operations on an address, but more complicated operations are often not supported. Calculating the difference between two labels usually works only when they are defined in the same section, and sometimes not even then:

      .dc.a label1 - 2      /* This will work. */
      .dc.a label1 - label2 /* This might not work. */
    

    Problems with instructions

    Typically, instructions are specific to individual architectures. As a result, you cannot write a generic assembler source file that involves code. Starting with GAS 2.35 however, there is a new pseudo-op instruction (.nop), which generates a no-op instruction on any target:

      .text
      .nop /* This is a real instruction. */
    

    Problems with sections

    All architectures accept the section names .text, .data, and .bss. The old AOUT file format only supports these names. More modern formats such as Portable Executable (PE) and Executable and Linkable Format (ELF) support arbitrary section names. When defining new sections, be aware that the .section directive for ELF targets accepts more arguments than does the PE version:

      .section name                  /* See note 1. */
      .section name, "flags"         /* See note 2. */
      .section name, "flags", %type  /* See note 3. */
    

    Notes:

    1. This form fails on targets where the section flags are compulsory.
    2. This form works for both PE-based and ELF-based targets, although the flags are different.
    3. This form only works on ELF-based targets. Note the use of the % character instead of the @ character.

    Conclusion

    This article addressed common problems writing portable assembly code and provided solutions and examples. In summary, writing portable assembler is hard to do and best kept simple, and persistence is the key.

    Last updated: February 5, 2024

    Recent Posts

    • Every layer counts: Defense in depth for AI agents with Red Hat AI

    • Fun in the RUN instruction: Why container builds with distroless images can surprise you

    • Trusted software factory: Building trust in the agentic AI era

    • Build a zero trust AI pipeline with OpenShift and RHEL CVMs

    • Red Hat Hardened Images: Top 5 benefits for software developers

    Red Hat Developers logo LinkedIn YouTube Twitter Facebook

    Platforms

    • Red Hat AI
    • Red Hat Enterprise Linux
    • Red Hat OpenShift
    • Red Hat Ansible Automation Platform
    • See all products

    Build

    • Developer Sandbox
    • Developer tools
    • Interactive tutorials
    • API catalog

    Quicklinks

    • Learning resources
    • E-books
    • Cheat sheets
    • Blog
    • Events
    • Newsletter

    Communicate

    • About us
    • Contact sales
    • Find a partner
    • Report a website issue
    • Site status dashboard
    • Report a security problem

    RED HAT DEVELOPER

    Build here. Go anywhere.

    We serve the builders. The problem solvers who create careers with code.

    Join us if you’re a developer, software engineer, web designer, front-end designer, UX designer, computer scientist, architect, tester, product manager, project manager or team lead.

    Sign me up

    Red Hat legal and privacy links

    • About Red Hat
    • Jobs
    • Events
    • Locations
    • Contact Red Hat
    • Red Hat Blog
    • Inclusion at Red Hat
    • Cool Stuff Store
    • Red Hat Summit
    © 2026 Red Hat

    Red Hat legal and privacy links

    • Privacy statement
    • Terms of use
    • All policies and guidelines
    • Digital accessibility

    Chat Support

    Please log in with your Red Hat account to access chat support.