This commit is contained in:
Isuru Samarathunga
2025-10-16 23:01:34 +05:30
5551 changed files with 1752068 additions and 2 deletions

View File

@ -0,0 +1,150 @@
# Changelog
Report issues to [GitHub].
For Android Studio issues, go to https://b.android.com and file a bug using the
Android Studio component, not the NDK component.
If you're a build system maintainer that needs to use the tools in the NDK
directly, see the [build system maintainers guide].
[GitHub]: https://github.com/android/ndk/issues
[build system maintainers guide]: https://android.googlesource.com/platform/ndk/+/master/docs/BuildSystemMaintainers.md
## Announcements
* Android V will allow OEMs to ship arm64-v8a devices with 16KiB page sizes.
Devices that use this configuration will not be able to run existing apps that
use native code. To be compatible with these devices, applications will need
to rebuild all their native code to be 16KiB aligned, and rewrite any code
which assumes a specific page size. ndk-build and CMake have options to enable
this mode (see note about `APP_SUPPORT_FLEXIBLE_PAGE_SIZES` and
`ANDROID_SUPPORT_FLEXIBLE_PAGE_SIZES` below). A future version of the NDK will
enable this mode by default. If you're using or maintaining a third-party
build system, consult the [build system maintainers guide] for instructions.
See [Support 16 KB page sizes] for more information.
[Support 16 KB page sizes]: https://developer.android.com/guide/practices/page-sizes
## r27d
* Updated LLVM to clang-r522817d. See `clang_source_info.md` in the toolchain
directory for version information.
* [Issue 2144]: Fixed issue where `lldb.sh` did not work when installed to a
path which contained spaces.
* [Issue 2143]: Fixed lldb.sh not finding libpython on macOS.
[Issue 2143]: https://github.com/android/ndk/issues/2143
[Issue 2144]: https://github.com/android/ndk/issues/2144
## r27c
* Updated LLVM to clang-r522817c. See `clang_source_info.md` in the toolchain
directory for version information.
* [Issue 2040]: Further fixes for miscompiles of indirect gotos.
* [Issue 2064]: Fix HWAsan miscompilation.
* [Issue 2070]: Fix crash in instantiation of function definitions.
* [Issue 2084]: Fix for incorrect compiler error on overloaded member
functions with ref-qualifiers.
[Issue 2040]: https://github.com/android/ndk/issues/2040
[Issue 2064]: https://github.com/android/ndk/issues/2064
[Issue 2070]: https://github.com/android/ndk/issues/2070
[Issue 2084]: https://github.com/android/ndk/issues/2084
## r27b
* Updated LLVM to clang-r522817b. See `clang_source_info.md` in the toolchain
directory for version information.
* [Issue 2040]: Fixed miscompile with indirect gotos.
* [Issue 2054]: Fixed crash in x86 fp16 instruction selection.
* [Issue 2059]: Fixed infinite loop in aarch64 vector instruction selection.
* [Issue 2032]: Fixed compatibility issues with projects that used a very old
`cmake_minimum_required` version ("Policy CMP0057 is not set: Support new
IN_LIST if() operator").
* [Issue 2039]: Fixed `LOCAL_STRIP_MODE` not being reset by
`include $(CLEAR_VARS)`.
* [Issue 2049]: Restored metadata used by CMake in non-toolchain-file use cases
that used `CMAKE_SYSTEM_PROCESSOR` instead of `CMAKE_ANDROID_ARCH_ABI`. If you
ran into this problem, you should switch to using `CMAKE_ANDROID_ARCH_ABI`
because that's what [CMake's docs] say to use. Better still, use the NDK's
[toolchain file].
[Issue 2032]: https://github.com/android/ndk/issues/2032
[Issue 2039]: https://github.com/android/ndk/issues/2039
[Issue 2040]: https://github.com/android/ndk/issues/2040
[Issue 2049]: https://github.com/android/ndk/issues/2049
[Issue 2054]: https://github.com/android/ndk/issues/2054
[Issue 2059]: https://github.com/android/ndk/issues/2059
[CMake's docs]: https://cmake.org/cmake/help/latest/manual/cmake-toolchains.7.html#cross-compiling-for-android-with-the-ndk
[toolchain file]: https://developer.android.com/ndk/guides/cmake
## Changes
* Updated LLVM to clang-r522817. See `clang_source_info.md` in the toolchain
directory for version information.
* [Issue 1728]: Clang now emits an error for invalid Android target versions.
* [Issue 1853]: `clang-scan-deps` is now included.
* [Issue 1911]: Fixed inconsistent `std::regex_replace` results.
* [Issue 1947]: Improved support for AArch64 function multi-versioning in clang.
* [Issue 1963]: Fixed undefined behavior in `std::unexpected::has_value()`.
* [Issue 1988]: Added aarch64 support for `preserve_all` calling convention.
* [Issue 2007]: Fixed crash in class template argument deduction caused by
self-referential friend declaration.
* [Issue 2010]: Removed superfluous libraries to reduce disk use.
* [Issue 2012]: Fixed front end crash when using concepts and modules.
* [Issue 2013]: Fixed false positive ODR violation in global module fragments.
* [Issue 2023]: Fixed Clang crashes related to lambda captures in unevaluated
contexts.
* [Issue 2024]: Removed invalid `__attribute__((__const__))` from `gettid`.
* A RISC-V sysroot (AKA riscv64, or rv64) has been added. It is **not**
supported. It is present to aid bringup for OS vendors, but it's not yet a
supported Android ABI. It will not be built by default.
* [Issue 1856]: Target-prefixed cmd wrappers for clang should now behave
appropriately when the first argument includes quotes. **You probably do not
need to use those wrappers.** In most cases where you would use
`aarch64-linux-android21-clang`, you can instead use `clang -target
aarch64-linux-android21`, e.g. `CC="clang -target aarch64-linux-android21"
./configure`. The wrappers are only needed when working with systems that do
not properly handle a `CC` that includes arguments.
* [Issue 1898]: ndk-stack now tolerates 0x prefixed addresses.
* [Issue 1921]: `ANDROID_USE_LEGACY_TOOLCHAIN_FILE` value is now preserved
during try-compile steps when `ON`.
* [Issue 1974]: Unintentionally shipped Vulkan headers have been removed from
`sources/third_party/vulkan`. The standard Vulkan headers are included in the
Android sysroot, which Clang will find automatically.
* [Issue 1993]: ndk-stack now tolerates invalid UTF-8 characters in the trace.
* [Issue 1994]: Fixed ndk-gdb/ndk-lldb to use the correct path for
make and other tools.
* Added `APP_SUPPORT_FLEXIBLE_PAGE_SIZES` for ndk-build and
`ANDROID_SUPPORT_FLEXIBLE_PAGE_SIZES` for CMake. Set to
`APP_SUPPORT_FLEXIBLE_PAGE_SIZES := true` in your `Application.mk` or pass
`-DANDROID_SUPPORT_FLEXIBLE_PAGE_SIZES=ON` to CMake (via
`android.defaultConfig.externalNativeBuild.cmake.arguments` if you're using
the Android Gradle Plugin) to build your code to be compatible with devices
that use a 16KiB page size. Third-party build system users and maintainers
should consult the [build system maintainers guide].
* Symlinks are now properly preserved in the macOS App Bundle. The NDK installed
via that method is now the same size as the one installed via the SDK manager.
* The unsupported libclang, libclang-cpp, libLLVM, and libLTO libraries were
removed to save space.
[Issue 1728]: https://github.com/android/ndk/issues/1728
[Issue 1853]: https://github.com/android/ndk/issues/1853
[Issue 1856]: https://github.com/android/ndk/issues/1856
[Issue 1898]: https://github.com/android/ndk/issues/1898
[Issue 1911]: https://github.com/android/ndk/issues/1911
[Issue 1921]: https://github.com/android/ndk/issues/1921
[Issue 1947]: https://github.com/android/ndk/issues/1947
[Issue 1963]: https://github.com/android/ndk/issues/1963
[Issue 1974]: https://github.com/android/ndk/issues/1974
[Issue 1988]: https://github.com/android/ndk/issues/1988
[Issue 1993]: https://github.com/android/ndk/issues/1993
[Issue 1994]: https://github.com/android/ndk/issues/1994
[Issue 2007]: https://github.com/android/ndk/issues/2007
[Issue 2010]: https://github.com/android/ndk/issues/2010
[Issue 2012]: https://github.com/android/ndk/issues/2012
[Issue 2013]: https://github.com/android/ndk/issues/2013
[Issue 2023]: https://github.com/android/ndk/issues/2023
[Issue 2024]: https://github.com/android/ndk/issues/2024

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,24 @@
Android NDK
===========
Documentation
-------------
NDK documentation, guides, and API reference are available on
[our website](https://developer.android.com/ndk/index.html).
NDK code samples are available on
[GitHub](https://github.com/googlesamples/android-ndk).
Information about Android Studio can be found on [the Android Studio
website](https://developer.android.com/studio/index.html).
Filing Bugs
-----------
NDK bugs should be filed on
[GitHub](https://github.com/android-ndk/ndk/issues/new).
Android Studio and Gradle bugs should be filed in the [Android Studio Bug
Tracker](http://b.android.com). For the fastest response, make sure you follow
their guide on [Filing Bugs](http://tools.android.com/filing-bugs).

View File

@ -0,0 +1,52 @@
{
"armeabi-v7a": {
"bitness": 32,
"default": true,
"deprecated": false,
"proc": "armv7-a",
"arch": "arm",
"triple": "arm-linux-androideabi",
"llvm_triple": "armv7-none-linux-androideabi",
"min_os_version": 21
},
"arm64-v8a": {
"bitness": 64,
"default": true,
"deprecated": false,
"proc": "aarch64",
"arch": "arm64",
"triple": "aarch64-linux-android",
"llvm_triple": "aarch64-none-linux-android",
"min_os_version": 21
},
"riscv64": {
"bitness": 64,
"default": false,
"deprecated": false,
"proc": "riscv64",
"arch": "riscv64",
"triple": "riscv64-linux-android",
"llvm_triple": "riscv64-none-linux-android",
"min_os_version": 35
},
"x86": {
"bitness": 32,
"default": true,
"deprecated": false,
"proc": "i686",
"arch": "x86",
"triple": "i686-linux-android",
"llvm_triple": "i686-none-linux-android",
"min_os_version": 21
},
"x86_64": {
"bitness": 64,
"default": true,
"deprecated": false,
"proc": "x86_64",
"arch": "x86_64",
"triple": "x86_64-linux-android",
"llvm_triple": "x86_64-none-linux-android",
"min_os_version": 21
}
}

View File

@ -0,0 +1,27 @@
{
"min": 21,
"max": 35,
"aliases": {
"20": 19,
"25": 24,
"J": 16,
"J-MR1": 17,
"J-MR2": 18,
"K": 19,
"L": 21,
"L-MR1": 22,
"M": 23,
"N": 24,
"N-MR1": 24,
"O": 26,
"O-MR1": 27,
"P": 28,
"Q": 29,
"R": 30,
"S": 31,
"Sv2": 32,
"Tiramisu": 33,
"UpsideDownCake": 34,
"VanillaIceCream": 35
}
}

View File

@ -0,0 +1,27 @@
{
"libEGL.so": "21",
"libGLESv1_CM.so": "21",
"libGLESv2.so": "21",
"libGLESv3.so": "21",
"libOpenMAXAL.so": "21",
"libOpenSLES.so": "21",
"libaaudio.so": "26",
"libamidi.so": "29",
"libandroid.so": "21",
"libbinder_ndk.so": "29",
"libc.so": "21",
"libcamera2ndk.so": "24",
"libdl.so": "21",
"libicu.so": "31",
"libjnigraphics.so": "21",
"liblog.so": "21",
"libm.so": "21",
"libmediandk.so": "21",
"libnativehelper.so": "31",
"libnativewindow.so": "26",
"libneuralnetworks.so": "27",
"libstdc++.so": "21",
"libsync.so": "26",
"libvulkan.so": "24",
"libz.so": "21"
}

View File

@ -0,0 +1,7 @@
@echo off
rem https://stackoverflow.com/a/29057742/632035
for /f "tokens=2" %%a in ("%~dp0") do (
echo ERROR: NDK path cannot contain spaces
exit /b 1
)
%~dp0build\ndk-build.cmd %*

View File

@ -0,0 +1,2 @@
@echo off
%~dp0prebuilt\windows-x86_64\bin\ndk-gdb.cmd %*

View File

@ -0,0 +1,2 @@
@echo off
%~dp0prebuilt\windows-x86_64\bin\ndk-gdb.cmd %*

View File

@ -0,0 +1,2 @@
@echo off
%~dp0prebuilt\windows-x86_64\bin\ndk-stack.cmd %*

View File

@ -0,0 +1,2 @@
@echo off
%~dp0prebuilt\windows-x86_64\bin\ndk-which %*

View File

@ -0,0 +1,5 @@
@echo off
setlocal
set ANDROID_NDK_PYTHON=%~dp0..\..\..\toolchains\llvm\prebuilt\windows-x86_64\python3\python.exe
set SHELL=cmd
"%ANDROID_NDK_PYTHON%" -u "%~dp0ndkgdb.pyz" %*

View File

@ -0,0 +1,5 @@
@echo off
setlocal
set ANDROID_NDK_PYTHON=%~dp0..\..\..\toolchains\llvm\prebuilt\windows-x86_64\python3\python.exe
set SHELL=cmd
"%ANDROID_NDK_PYTHON%" -u "%~dp0ndkstack.pyz" %*

View File

@ -0,0 +1,110 @@
#!/usr/bin/env bash
#
# Copyright (C) 2012 The Android Open Source Project
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
#
# DEPRECATED
#
# This script shows the path of the active toolchain components within
# the ndk. This was necessary for GCC and binutils, where each ABI had
# its own tools, but is not needed for LLVM-based tools which should be
# used in preference.
usage() {
echo "USAGE: ndk-which [--abi ABI] TOOL"
echo "ABI is 'armeabi-v7a', 'arm64-v8a', 'x86', or 'x86_64'"
echo "TOOL is 'gdb', 'objdump', 'readelf', etc."
echo
echo "Note that LLVM replacements for binutils tools work for all ABIs."
exit 1
}
error() {
echo "The tool: $1 doesn't exist"
echo "Possible choices are: "
count=0
for file in $2*
do
if [[ $file == *$1 ]]
then
echo $file
((count = count + 1))
fi
done
if [ $count -eq 0 ]
then
echo " None "
fi
exit 1
}
ABI=armeabi-v7a
while (( "$#" )); do
case "$1" in
--abi)
ABI=$2
shift 2
abis='^(armeabi-v7a|arm64-v8a|x86|x86_64)$'
if [[ ! "$ABI" =~ $abis ]]; then usage; fi
;;
*)
break
;;
esac
done
TOOL=$1
shift
if [ "$#" != 0 -o "$TOOL" == "" ]; then
usage
fi
# This tool is installed in prebuilt/linux-x86_64/bin/.
MYNDKDIR=`dirname $0`/../../..
# create a temporary skeleton project so that we can leverage build-local.mk
TMPDIR=/tmp/ndk-which-$$
mkdir -p $TMPDIR/jni
cat >$TMPDIR/jni/Android.mk << "END_OF_FILE"
include $(CLEAR_VARS)
END_OF_FILE
get_build_var_for_abi() {
if [ -z "$GNUMAKE" ] ; then
GNUMAKE=make
fi
NDK_PROJECT_PATH=$TMPDIR $GNUMAKE --no-print-dir -f $MYNDKDIR/build/core/build-local.mk DUMP_$1 APP_ABI=$2
}
LLVM_TOOLCHAIN_PREFIX=`get_build_var_for_abi LLVM_TOOLCHAIN_PREFIX $ABI`
TOOLCHAIN_PREFIX=`get_build_var_for_abi TOOLCHAIN_PREFIX $ABI`
rm -Rf $TMPDIR
# fully qualified file name
FQFN=${TOOLCHAIN_PREFIX}$TOOL
# use the host system's 'which' to decide/report if the file exists or not, and is executable
if [ ! -f $FQFN ]
then
FQFN=${LLVM_TOOLCHAIN_PREFIX}llvm-$TOOL
if [ ! -f $FQFN ]
then
error $TOOL $LLVM_TOOLCHAIN_PREFIX
fi
fi
which "$FQFN"

View File

@ -0,0 +1,9 @@
#ifndef _YASM_LIBYASM_STDINT_H
#define _YASM_LIBYASM_STDINT_H 1
#ifndef _GENERATED_STDINT_H
#define _GENERATED_STDINT_H "yasm 1.3.0"
/* generated using /mnt/disks/build-disk/src/android/ndk-r27-release/prebuilts/clang/host/linux-x86/clang-r522817d/bin/clang --target=x86_64-w64-mingw32 -I/mnt/disks/build-disk/src/android/ndk-r27-release/prebuilts/clang/host/windows-x86/clang-r522817d/include/c++/v1 --sysroot=/mnt/disks/build-disk/src/android/ndk-r27-release/prebuilts/gcc/linux-x86/host/x86_64-w64-mingw32-4.8/x86_64-w64-mingw32 -L/mnt/disks/build-disk/src/android/ndk-r27-release/prebuilts/gcc/linux-x86/host/x86_64-w64-mingw32-4.8/lib/gcc/x86_64-w64-mingw32/4.8.3 -B/mnt/disks/build-disk/src/android/ndk-r27-release/prebuilts/gcc/linux-x86/host/x86_64-w64-mingw32-4.8/lib/gcc/x86_64-w64-mingw32/4.8.3 -L/mnt/disks/build-disk/src/android/ndk-r27-release/prebuilts/gcc/linux-x86/host/x86_64-w64-mingw32-4.8/x86_64-w64-mingw32/lib64 -B/mnt/disks/build-disk/src/android/ndk-r27-release/prebuilts/gcc/linux-x86/host/x86_64-w64-mingw32-4.8/x86_64-w64-mingw32/lib64 -L/mnt/disks/build-disk/src/android/ndk-r27-release/prebuilts/clang/host/windows-x86/clang-r522817d/lib64 -B/mnt/disks/build-disk/src/android/ndk-r27-release/prebuilts/clang/host/windows-x86/clang-r522817d/lib64 -Os -fomit-frame-pointer -w -fuse-ld=lld -s */
#define _STDINT_HAVE_STDINT_H 1
#include <stdint.h>
#endif
#endif

View File

@ -0,0 +1,75 @@
/**
* \file libyasm.h
* \brief YASM library primary header file.
*
* \license
* Copyright (C) 2003-2007 Peter Johnson
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* - Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* - Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND OTHER CONTRIBUTORS ``AS IS''
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR OTHER CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
* \endlicense
*/
#ifndef YASM_LIB_H
#define YASM_LIB_H
#ifdef YASM_PYXELATOR
typedef struct __FILE FILE;
typedef struct __va_list va_list;
typedef unsigned long size_t;
typedef unsigned long uintptr_t;
#else
#include <stdio.h>
#include <stdarg.h>
#include <libyasm-stdint.h>
#endif
#include <libyasm/compat-queue.h>
#include <libyasm/coretype.h>
#include <libyasm/valparam.h>
#include <libyasm/linemap.h>
#include <libyasm/errwarn.h>
#include <libyasm/intnum.h>
#include <libyasm/floatnum.h>
#include <libyasm/expr.h>
#include <libyasm/value.h>
#include <libyasm/symrec.h>
#include <libyasm/bytecode.h>
#include <libyasm/section.h>
#include <libyasm/insn.h>
#include <libyasm/arch.h>
#include <libyasm/dbgfmt.h>
#include <libyasm/objfmt.h>
#include <libyasm/listfmt.h>
#include <libyasm/parser.h>
#include <libyasm/preproc.h>
#include <libyasm/file.h>
#include <libyasm/module.h>
#include <libyasm/hamt.h>
#include <libyasm/md5.h>
#endif

View File

@ -0,0 +1,495 @@
/**
* \file libyasm/arch.h
* \brief YASM architecture interface.
*
* \license
* Copyright (C) 2002-2007 Peter Johnson
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* - Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* - Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND OTHER CONTRIBUTORS ``AS IS''
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR OTHER CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
* \endlicense
*/
#ifndef YASM_ARCH_H
#define YASM_ARCH_H
/** Errors that may be returned by yasm_arch_module::create(). */
typedef enum yasm_arch_create_error {
YASM_ARCH_CREATE_OK = 0, /**< No error. */
YASM_ARCH_CREATE_BAD_MACHINE, /**< Unrecognized machine name. */
YASM_ARCH_CREATE_BAD_PARSER /**< Unrecognized parser name. */
} yasm_arch_create_error;
/** Return values for yasm_arch_module::parse_check_insnprefix(). */
typedef enum yasm_arch_insnprefix {
YASM_ARCH_NOTINSNPREFIX = 0, /**< Unrecognized */
YASM_ARCH_INSN, /**< An instruction */
YASM_ARCH_PREFIX /**< An instruction prefix */
} yasm_arch_insnprefix;
/** Types of registers / target modifiers that may be returned by
* yasm_arch_module::parse_check_regtmod().
*/
typedef enum yasm_arch_regtmod {
YASM_ARCH_NOTREGTMOD = 0, /**< Unrecognized */
YASM_ARCH_REG, /**< A "normal" register */
YASM_ARCH_REGGROUP, /**< A group of indexable registers */
YASM_ARCH_SEGREG, /**< A segment register */
YASM_ARCH_TARGETMOD /**< A target modifier (for jumps) */
} yasm_arch_regtmod;
#ifndef YASM_DOXYGEN
/** Base #yasm_arch structure. Must be present as the first element in any
* #yasm_arch implementation.
*/
typedef struct yasm_arch_base {
/** #yasm_arch_module implementation for this architecture. */
const struct yasm_arch_module *module;
} yasm_arch_base;
#endif
/** YASM machine subtype. A number of different machine types may be
* associated with a single architecture. These may be specific CPU's, but
* the ABI used to interface with the architecture should be the primary
* differentiator between machines. Some object formats (ELF) use the machine
* to determine parameters within the generated output.
*/
typedef struct yasm_arch_machine {
/** One-line description of the machine. */
const char *name;
/** Keyword used to select machine. */
const char *keyword;
} yasm_arch_machine;
/** YASM architecture module interface.
* \note All "data" in parser-related functions (yasm_arch_parse_*) needs to
* start the parse initialized to 0 to make it okay for a parser-related
* function to use/check previously stored data to see if it's been
* called before on the same piece of data.
*/
typedef struct yasm_arch_module {
/** One-line description of the architecture.
* Call yasm_arch_name() to get the name of a particular #yasm_arch.
*/
const char *name;
/** Keyword used to select architecture.
* Call yasm_arch_keyword() to get the keyword of a particular #yasm_arch.
*/
const char *keyword;
/** NULL-terminated list of directives. NULL if none. */
/*@null@*/ const yasm_directive *directives;
/** Create architecture.
* Module-level implementation of yasm_arch_create().
* Call yasm_arch_create() instead of calling this function.
*/
/*@only@*/ yasm_arch * (*create) (const char *machine, const char *parser,
/*@out@*/ yasm_arch_create_error *error);
/** Module-level implementation of yasm_arch_destroy().
* Call yasm_arch_destroy() instead of calling this function.
*/
void (*destroy) (/*@only@*/ yasm_arch *arch);
/** Module-level implementation of yasm_arch_get_machine().
* Call yasm_arch_get_machine() instead of calling this function.
*/
const char * (*get_machine) (const yasm_arch *arch);
/** Module-level implementation of yasm_arch_get_address_size().
* Call yasm_arch_get_address_size() instead of calling this function.
*/
unsigned int (*get_address_size) (const yasm_arch *arch);
/** Module-level implementation of yasm_arch_set_var().
* Call yasm_arch_set_var() instead of calling this function.
*/
int (*set_var) (yasm_arch *arch, const char *var, unsigned long val);
/** Module-level implementation of yasm_arch_parse_check_insnprefix().
* Call yasm_arch_parse_check_insnprefix() instead of calling this function.
*/
yasm_arch_insnprefix (*parse_check_insnprefix)
(yasm_arch *arch, const char *id, size_t id_len, unsigned long line,
/*@out@*/ /*@only@*/ yasm_bytecode **bc, /*@out@*/ uintptr_t *prefix);
/** Module-level implementation of yasm_arch_parse_check_regtmod().
* Call yasm_arch_parse_check_regtmod() instead of calling this function.
*/
yasm_arch_regtmod (*parse_check_regtmod)
(yasm_arch *arch, const char *id, size_t id_len,
/*@out@*/ uintptr_t *data);
/** Module-level implementation of yasm_arch_get_fill().
* Call yasm_arch_get_fill() instead of calling this function.
*/
const unsigned char ** (*get_fill) (const yasm_arch *arch);
/** Module-level implementation of yasm_arch_floatnum_tobytes().
* Call yasm_arch_floatnum_tobytes() instead of calling this function.
*/
int (*floatnum_tobytes) (yasm_arch *arch, const yasm_floatnum *flt,
unsigned char *buf, size_t destsize,
size_t valsize, size_t shift, int warn);
/** Module-level implementation of yasm_arch_intnum_tobytes().
* Call yasm_arch_intnum_tobytes() instead of calling this function.
*/
int (*intnum_tobytes) (yasm_arch *arch, const yasm_intnum *intn,
unsigned char *buf, size_t destsize, size_t valsize,
int shift, const yasm_bytecode *bc,
int warn);
/** Module-level implementation of yasm_arch_get_reg_size().
* Call yasm_arch_get_reg_size() instead of calling this function.
*/
unsigned int (*get_reg_size) (yasm_arch *arch, uintptr_t reg);
/** Module-level implementation of yasm_arch_reggroup_get_reg().
* Call yasm_arch_reggroup_get_reg() instead of calling this function.
*/
uintptr_t (*reggroup_get_reg) (yasm_arch *arch, uintptr_t reggroup,
unsigned long regindex);
/** Module-level implementation of yasm_arch_reg_print().
* Call yasm_arch_reg_print() instead of calling this function.
*/
void (*reg_print) (yasm_arch *arch, uintptr_t reg, FILE *f);
/** Module-level implementation of yasm_arch_segreg_print().
* Call yasm_arch_segreg_print() instead of calling this function.
*/
void (*segreg_print) (yasm_arch *arch, uintptr_t segreg, FILE *f);
/** Module-level implementation of yasm_arch_ea_create().
* Call yasm_arch_ea_create() instead of calling this function.
*/
yasm_effaddr * (*ea_create) (yasm_arch *arch, /*@keep@*/ yasm_expr *e);
/** Module-level implementation of yasm_arch_ea_destroy().
* Call yasm_arch_ea_destroy() instead of calling this function.
*/
void (*ea_destroy) (/*@only@*/ yasm_effaddr *ea);
/** Module-level implementation of yasm_arch_ea_print().
* Call yasm_arch_ea_print() instead of calling this function.
*/
void (*ea_print) (const yasm_effaddr *ea, FILE *f, int indent_level);
/** Module-level implementation of yasm_arch_create_empty_insn().
* Call yasm_arch_create_empty_insn() instead of calling this function.
*/
/*@only@*/ yasm_bytecode * (*create_empty_insn) (yasm_arch *arch,
unsigned long line);
/** NULL-terminated list of machines for this architecture.
* Call yasm_arch_get_machine() to get the active machine of a particular
* #yasm_arch.
*/
const yasm_arch_machine *machines;
/** Default machine keyword.
* Call yasm_arch_get_machine() to get the active machine of a particular
* #yasm_arch.
*/
const char *default_machine_keyword;
/** Canonical "word" size in bits.
* Call yasm_arch_wordsize() to get the word size of a particular
* #yasm_arch.
*/
unsigned int wordsize;
/** Worst case minimum instruction length in bytes.
* Call yasm_arch_min_insn_len() to get the minimum instruction length of
* a particular #yasm_arch.
*/
unsigned int min_insn_len;
} yasm_arch_module;
/** Get the one-line description of an architecture.
* \param arch architecture
* \return One-line description of architecture.
*/
const char *yasm_arch_name(const yasm_arch *arch);
/** Get the keyword used to select an architecture.
* \param arch architecture
* \return Architecture keyword.
*/
const char *yasm_arch_keyword(const yasm_arch *arch);
/** Get the word size of an architecture.
* \param arch architecture
* \return Word size (in bits).
*/
unsigned int yasm_arch_wordsize(const yasm_arch *arch);
/** Get the minimum instruction length of an architecture.
* \param arch architecture
* \return Minimum instruction length (in bytes).
*/
unsigned int yasm_arch_min_insn_len(const yasm_arch *arch);
/** Create architecture.
* \param module architecture module
* \param machine keyword of machine in use (must be one listed in
* #yasm_arch_module.machines)
* \param parser keyword of parser in use
* \param error error return value
* \return NULL on error (error returned in error parameter), otherwise new
* architecture.
*/
/*@only@*/ yasm_arch *yasm_arch_create(const yasm_arch_module *module,
const char *machine, const char *parser,
/*@out@*/ yasm_arch_create_error *error);
/** Clean up, free any architecture-allocated memory.
* \param arch architecture
*/
void yasm_arch_destroy(/*@only@*/ yasm_arch *arch);
/** Get architecture's active machine name.
* \param arch architecture
* \return Active machine name.
*/
const char *yasm_arch_get_machine(const yasm_arch *arch);
/** Get architecture's active address size, in bits.
* \param arch architecture
* \return Active address size (in bits).
*/
unsigned int yasm_arch_get_address_size(const yasm_arch *arch);
/** Set any arch-specific variables. For example, "mode_bits" in x86.
* \param arch architecture
* \param var variable name
* \param val value to set
* \return Zero on success, non-zero on failure (variable does not exist).
*/
int yasm_arch_set_var(yasm_arch *arch, const char *var, unsigned long val);
/** Check an generic identifier to see if it matches architecture specific
* names for instructions or instruction prefixes. Unrecognized identifiers
* should return #YASM_ARCH_NOTINSNPREFIX so they can be treated as normal
* symbols. Any additional data beyond just the type (almost always necessary)
* should be returned into the space provided by the data parameter.
* \param arch architecture
* \param id identifier as in the input file
* \param id_len length of id string
* \param line virtual line
* \param bc for instructions, yasm_insn-based bytecode is returned
* (and NULL otherwise)
* \param prefix for prefixes, yasm_arch-specific value is returned
* (and 0 otherwise)
* \return Identifier type (#YASM_ARCH_NOTINSNPREFIX if unrecognized)
*/
yasm_arch_insnprefix yasm_arch_parse_check_insnprefix
(yasm_arch *arch, const char *id, size_t id_len, unsigned long line,
/*@out@*/ /*@only@*/ yasm_bytecode **bc, /*@out@*/ uintptr_t *prefix);
/** Check an generic identifier to see if it matches architecture specific
* names for registers or target modifiers. Unrecognized identifiers should
* return #YASM_ARCH_NOTREGTMOD. Any additional data beyond just the type
* (almost always necessary) should be returned into the space provided by the
* data parameter.
* \param arch architecture
* \param id identifier as in the input file
* \param id_len length of id string
* \param data extra identification information (yasm_arch-specific)
* [output]
* \return Identifier type (#YASM_ARCH_NOTREGTMOD if unrecognized)
*/
yasm_arch_regtmod yasm_arch_parse_check_regtmod
(yasm_arch *arch, const char *id, size_t id_len,
/*@out@*/ uintptr_t *data);
/** Get NOP fill patterns for 1-15 bytes of fill.
* \param arch architecture
* \return 16-entry array of arrays; [0] is unused, [1] - [15] point to arrays
* of 1-15 bytes (respectively) in length.
*/
const unsigned char **yasm_arch_get_fill(const yasm_arch *arch);
/** Output #yasm_floatnum to buffer. Puts the value into the least
* significant bits of the destination, or may be shifted into more
* significant bits by the shift parameter. The destination bits are
* cleared before being set.
* Architecture-specific because of endianness.
* \param arch architecture
* \param flt floating point value
* \param buf buffer to write into
* \param destsize destination size (in bytes)
* \param valsize size (in bits)
* \param shift left shift (in bits)
* \param warn enables standard overflow/underflow warnings
* \return Nonzero on error.
*/
int yasm_arch_floatnum_tobytes(yasm_arch *arch, const yasm_floatnum *flt,
unsigned char *buf, size_t destsize,
size_t valsize, size_t shift, int warn);
/** Output #yasm_intnum to buffer. Puts the value into the least
* significant bits of the destination, or may be shifted into more
* significant bits by the shift parameter. The destination bits are
* cleared before being set.
* \param arch architecture
* \param intn integer value
* \param buf buffer to write into
* \param destsize destination size (in bytes)
* \param valsize size (in bits)
* \param shift left shift (in bits); may be negative to specify right
* shift (standard warnings include truncation to boundary)
* \param bc bytecode being output ("parent" of value)
* \param warn enables standard warnings (value doesn't fit into
* valsize bits)
* \return Nonzero on error.
*/
int yasm_arch_intnum_tobytes(yasm_arch *arch, const yasm_intnum *intn,
unsigned char *buf, size_t destsize,
size_t valsize, int shift,
const yasm_bytecode *bc, int warn);
/** Get the equivalent size of a register in bits.
* \param arch architecture
* \param reg register
* \return 0 if there is no suitable equivalent size, otherwise the size.
*/
unsigned int yasm_arch_get_reg_size(yasm_arch *arch, uintptr_t reg);
/** Get a specific register of a register group, based on the register
* group and the index within the group.
* \param arch architecture
* \param reggroup register group
* \param regindex register index
* \return 0 if regindex is not valid for that register group, otherwise the
* specific register value.
*/
uintptr_t yasm_arch_reggroup_get_reg(yasm_arch *arch, uintptr_t reggroup,
unsigned long regindex);
/** Print a register. For debugging purposes.
* \param arch architecture
* \param reg register
* \param f file
*/
void yasm_arch_reg_print(yasm_arch *arch, uintptr_t reg, FILE *f);
/** Print a segment register. For debugging purposes.
* \param arch architecture
* \param segreg segment register
* \param f file
*/
void yasm_arch_segreg_print(yasm_arch *arch, uintptr_t segreg, FILE *f);
/** Create an effective address from an expression.
* \param arch architecture
* \param e expression (kept, do not delete)
* \return Newly allocated effective address.
*/
yasm_effaddr *yasm_arch_ea_create(yasm_arch *arch, /*@keep@*/ yasm_expr *e);
/** Delete (free allocated memory for) an effective address.
* \param arch architecture
* \param ea effective address (only pointer to it).
*/
void yasm_arch_ea_destroy(yasm_arch *arch, /*@only@*/ yasm_effaddr *ea);
/** Print an effective address. For debugging purposes.
* \param arch architecture
* \param ea effective address
* \param f file
* \param indent_level indentation level
*/
void yasm_arch_ea_print(const yasm_arch *arch, const yasm_effaddr *ea,
FILE *f, int indent_level);
/** Create a bytecode that represents a single empty (0 length) instruction.
* This is used for handling solitary prefixes.
* \param arch architecture
* \param line virtual line (from yasm_linemap)
* \return Newly allocated bytecode.
*/
/*@only@*/ yasm_bytecode *yasm_arch_create_empty_insn(yasm_arch *arch,
unsigned long line);
#ifndef YASM_DOXYGEN
/* Inline macro implementations for arch functions */
#define yasm_arch_name(arch) \
(((yasm_arch_base *)arch)->module->name)
#define yasm_arch_keyword(arch) \
(((yasm_arch_base *)arch)->module->keyword)
#define yasm_arch_wordsize(arch) \
(((yasm_arch_base *)arch)->module->wordsize)
#define yasm_arch_min_insn_len(arch) \
(((yasm_arch_base *)arch)->module->min_insn_len)
#define yasm_arch_create(module, machine, parser, error) \
module->create(machine, parser, error)
#define yasm_arch_destroy(arch) \
((yasm_arch_base *)arch)->module->destroy(arch)
#define yasm_arch_get_machine(arch) \
((yasm_arch_base *)arch)->module->get_machine(arch)
#define yasm_arch_get_address_size(arch) \
((yasm_arch_base *)arch)->module->get_address_size(arch)
#define yasm_arch_set_var(arch, var, val) \
((yasm_arch_base *)arch)->module->set_var(arch, var, val)
#define yasm_arch_parse_check_insnprefix(arch, id, id_len, line, bc, prefix) \
((yasm_arch_base *)arch)->module->parse_check_insnprefix \
(arch, id, id_len, line, bc, prefix)
#define yasm_arch_parse_check_regtmod(arch, id, id_len, data) \
((yasm_arch_base *)arch)->module->parse_check_regtmod \
(arch, id, id_len, data)
#define yasm_arch_get_fill(arch) \
((yasm_arch_base *)arch)->module->get_fill(arch)
#define yasm_arch_floatnum_tobytes(arch, flt, buf, destsize, valsize, shift, \
warn) \
((yasm_arch_base *)arch)->module->floatnum_tobytes \
(arch, flt, buf, destsize, valsize, shift, warn)
#define yasm_arch_intnum_tobytes(arch, intn, buf, destsize, valsize, shift, \
bc, warn) \
((yasm_arch_base *)arch)->module->intnum_tobytes \
(arch, intn, buf, destsize, valsize, shift, bc, warn)
#define yasm_arch_get_reg_size(arch, reg) \
((yasm_arch_base *)arch)->module->get_reg_size(arch, reg)
#define yasm_arch_reggroup_get_reg(arch, regg, regi) \
((yasm_arch_base *)arch)->module->reggroup_get_reg(arch, regg, regi)
#define yasm_arch_reg_print(arch, reg, f) \
((yasm_arch_base *)arch)->module->reg_print(arch, reg, f)
#define yasm_arch_segreg_print(arch, segreg, f) \
((yasm_arch_base *)arch)->module->segreg_print(arch, segreg, f)
#define yasm_arch_ea_create(arch, e) \
((yasm_arch_base *)arch)->module->ea_create(arch, e)
#define yasm_arch_ea_destroy(arch, ea) \
((yasm_arch_base *)arch)->module->ea_destroy(ea)
#define yasm_arch_ea_print(arch, ea, f, i) \
((yasm_arch_base *)arch)->module->ea_print(ea, f, i)
#define yasm_arch_create_empty_insn(arch, line) \
((yasm_arch_base *)arch)->module->create_empty_insn(arch, line)
#endif
#endif

View File

@ -0,0 +1,76 @@
/**
* \file assocdat.h
* \brief YASM associated data storage (libyasm internal use)
*
* \license
* Copyright (C) 2003-2007 Peter Johnson
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* - Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* - Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND OTHER CONTRIBUTORS ``AS IS''
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR OTHER CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
* \endlicense
*/
#ifndef YASM_ASSOCDAT_H
#define YASM_ASSOCDAT_H
#ifndef YASM_LIB_DECL
#define YASM_LIB_DECL
#endif
/** Associated data container. */
typedef struct yasm__assoc_data yasm__assoc_data;
/** Create an associated data container. */
YASM_LIB_DECL
/*@only@*/ yasm__assoc_data *yasm__assoc_data_create(void);
/** Get associated data for a data callback.
* \param assoc_data container of associated data
* \param callback callback used when adding data
* \return Associated data (NULL if none).
*/
YASM_LIB_DECL
/*@dependent@*/ /*@null@*/ void *yasm__assoc_data_get
(/*@null@*/ yasm__assoc_data *assoc_data,
const yasm_assoc_data_callback *callback);
/** Add associated data to a associated data container.
* \attention Deletes any existing associated data for that data callback.
* \param assoc_data container of associated data
* \param callback callback
* \param data data to associate
*/
YASM_LIB_DECL
/*@only@*/ yasm__assoc_data *yasm__assoc_data_add
(/*@null@*/ /*@only@*/ yasm__assoc_data *assoc_data,
const yasm_assoc_data_callback *callback,
/*@only@*/ /*@null@*/ void *data);
/** Destroy all associated data in a container. */
YASM_LIB_DECL
void yasm__assoc_data_destroy
(/*@null@*/ /*@only@*/ yasm__assoc_data *assoc_data);
/** Print all associated data in a container. */
YASM_LIB_DECL
void yasm__assoc_data_print(const yasm__assoc_data *assoc_data, FILE *f,
int indent_level);
#endif

View File

@ -0,0 +1,666 @@
#ifndef YASM_BITVECT_H
#define YASM_BITVECT_H
/*****************************************************************************/
/* MODULE NAME: BitVector.h MODULE TYPE: (adt) */
/*****************************************************************************/
/* MODULE IMPORTS: */
/*****************************************************************************/
/* ToolBox.h */
/*****************************************************************************/
/* NOTE: The type names that have been chosen here are somewhat weird on */
/* purpose, in order to avoid name clashes with system header files */
/* and your own application(s) which might - directly or indirectly - */
/* include this definitions file. */
/*****************************************************************************/
#ifndef YASM_LIB_DECL
#define YASM_LIB_DECL
#endif
typedef unsigned char N_char;
typedef unsigned char N_byte;
typedef unsigned short N_short;
typedef unsigned short N_shortword;
typedef unsigned int N_int;
typedef unsigned int N_word;
typedef unsigned long N_long;
typedef unsigned long N_longword;
/* Mnemonic 1: The natural numbers, N = { 0, 1, 2, 3, ... } */
/* Mnemonic 2: Nnnn = u_N_signed, _N_ot signed */
typedef signed char Z_char;
typedef signed char Z_byte;
typedef signed short Z_short;
typedef signed short Z_shortword;
typedef signed int Z_int;
typedef signed int Z_word;
typedef signed long Z_long;
typedef signed long Z_longword;
/* Mnemonic 1: The whole numbers, Z = { 0, -1, 1, -2, 2, -3, 3, ... } */
/* Mnemonic 2: Zzzz = Ssss_igned */
typedef void *voidptr;
typedef N_char *charptr;
typedef N_byte *byteptr;
typedef N_short *shortptr;
typedef N_shortword *shortwordptr;
typedef N_int *intptr;
typedef N_word *wordptr;
typedef N_long *longptr;
typedef N_longword *longwordptr;
typedef N_char *N_charptr;
typedef N_byte *N_byteptr;
typedef N_short *N_shortptr;
typedef N_shortword *N_shortwordptr;
typedef N_int *N_intptr;
typedef N_word *N_wordptr;
typedef N_long *N_longptr;
typedef N_longword *N_longwordptr;
typedef Z_char *Z_charptr;
typedef Z_byte *Z_byteptr;
typedef Z_short *Z_shortptr;
typedef Z_shortword *Z_shortwordptr;
typedef Z_int *Z_intptr;
typedef Z_word *Z_wordptr;
typedef Z_long *Z_longptr;
typedef Z_longword *Z_longwordptr;
#ifndef FALSE
#define FALSE (0!=0)
#endif
#ifndef TRUE
#define TRUE (0==0)
#endif
#ifdef __cplusplus
typedef bool boolean;
#else
#ifdef MACOS_TRADITIONAL
#define boolean Boolean
#else
typedef enum boolean { false = FALSE, true = TRUE } boolean;
#endif
#endif
/*****************************************************************************/
/* MODULE INTERFACE: */
/*****************************************************************************/
typedef enum ErrCode
{
ErrCode_Ok = 0, /* everything went allright */
ErrCode_Type, /* types word and size_t have incompatible sizes */
ErrCode_Bits, /* bits of word and sizeof(word) are inconsistent */
ErrCode_Word, /* size of word is less than 16 bits */
ErrCode_Long, /* size of word is greater than size of long */
ErrCode_Powr, /* number of bits of word is not a power of two */
ErrCode_Loga, /* error in calculation of logarithm */
ErrCode_Null, /* unable to allocate memory */
ErrCode_Indx, /* index out of range */
ErrCode_Ordr, /* minimum > maximum index */
ErrCode_Size, /* bit vector size mismatch */
ErrCode_Pars, /* input string syntax error */
ErrCode_Ovfl, /* numeric overflow error */
ErrCode_Same, /* operands must be distinct */
ErrCode_Expo, /* exponent must be positive */
ErrCode_Zero /* division by zero error */
} ErrCode;
typedef wordptr *listptr;
/* ===> MISCELLANEOUS BASIC FUNCTIONS: <=== */
YASM_LIB_DECL
const char * BitVector_Error (ErrCode error); /* return string for err code */
YASM_LIB_DECL
ErrCode BitVector_Boot (void); /* 0 = ok, 1..7 = error */
YASM_LIB_DECL
void BitVector_Shutdown (void); /* undo Boot */
YASM_LIB_DECL
N_word BitVector_Size (N_int bits); /* bit vector size (# of words) */
YASM_LIB_DECL
N_word BitVector_Mask (N_int bits); /* bit vector mask (unused bits) */
/* ===> CLASS METHODS: <=== */
YASM_LIB_DECL
const char * BitVector_Version (void); /* returns version string */
YASM_LIB_DECL
N_int BitVector_Word_Bits (void); /* return # of bits in machine word */
YASM_LIB_DECL
N_int BitVector_Long_Bits (void); /* return # of bits in unsigned long */
/* ===> CONSTRUCTOR METHODS: <=== */
YASM_LIB_DECL
/*@only@*/ wordptr BitVector_Create (N_int bits, boolean clear); /* malloc */
YASM_LIB_DECL
listptr BitVector_Create_List(N_int bits, boolean clear, N_int count);
YASM_LIB_DECL
wordptr BitVector_Resize (wordptr oldaddr, N_int bits); /* realloc */
YASM_LIB_DECL
wordptr BitVector_Shadow (wordptr addr); /* make new same size but empty */
YASM_LIB_DECL
wordptr BitVector_Clone (wordptr addr); /* make exact duplicate */
YASM_LIB_DECL
wordptr BitVector_Concat (wordptr X, wordptr Y); /* return concatenation */
/* ===> DESTRUCTOR METHODS: <=== */
YASM_LIB_DECL
void BitVector_Dispose (/*@only@*/ /*@out@*/ charptr string); /* string */
YASM_LIB_DECL
void BitVector_Destroy (/*@only@*/ wordptr addr); /* bitvec */
YASM_LIB_DECL
void BitVector_Destroy_List (listptr list, N_int count); /* list */
/* ===> OBJECT METHODS: <=== */
/* ===> bit vector copy function: */
YASM_LIB_DECL
void BitVector_Copy (wordptr X, wordptr Y); /* X = Y */
/* ===> bit vector initialization: */
YASM_LIB_DECL
void BitVector_Empty (wordptr addr); /* X = {} */
YASM_LIB_DECL
void BitVector_Fill (wordptr addr); /* X = ~{} */
YASM_LIB_DECL
void BitVector_Flip (wordptr addr); /* X = ~X */
YASM_LIB_DECL
void BitVector_Primes (wordptr addr);
/* ===> miscellaneous functions: */
YASM_LIB_DECL
void BitVector_Reverse (wordptr X, wordptr Y);
/* ===> bit vector interval operations and functions: */
YASM_LIB_DECL
void BitVector_Interval_Empty (/*@out@*/ wordptr addr, N_int lower, N_int upper);
YASM_LIB_DECL
void BitVector_Interval_Fill (/*@out@*/ wordptr addr, N_int lower, N_int upper);
YASM_LIB_DECL
void BitVector_Interval_Flip (/*@out@*/ wordptr addr, N_int lower, N_int upper);
YASM_LIB_DECL
void BitVector_Interval_Reverse (/*@out@*/ wordptr addr, N_int lower, N_int upper);
YASM_LIB_DECL
boolean BitVector_interval_scan_inc (wordptr addr, N_int start,
N_intptr min, N_intptr max);
YASM_LIB_DECL
boolean BitVector_interval_scan_dec (wordptr addr, N_int start,
N_intptr min, N_intptr max);
YASM_LIB_DECL
void BitVector_Interval_Copy (/*@out@*/ wordptr X, wordptr Y, N_int Xoffset,
N_int Yoffset, N_int length);
YASM_LIB_DECL
wordptr BitVector_Interval_Substitute(/*@out@*/ wordptr X, wordptr Y,
N_int Xoffset, N_int Xlength,
N_int Yoffset, N_int Ylength);
/* ===> bit vector test functions: */
YASM_LIB_DECL
boolean BitVector_is_empty (wordptr addr); /* X == {} ? */
YASM_LIB_DECL
boolean BitVector_is_full (wordptr addr); /* X == ~{} ? */
YASM_LIB_DECL
boolean BitVector_equal (wordptr X, wordptr Y); /* X == Y ? */
YASM_LIB_DECL
Z_int BitVector_Lexicompare(wordptr X, wordptr Y); /* X <,=,> Y ? */
YASM_LIB_DECL
Z_int BitVector_Compare (wordptr X, wordptr Y); /* X <,=,> Y ? */
/* ===> bit vector string conversion functions: */
YASM_LIB_DECL
/*@only@*/ charptr BitVector_to_Hex (wordptr addr);
YASM_LIB_DECL
ErrCode BitVector_from_Hex (/*@out@*/wordptr addr, charptr string);
YASM_LIB_DECL
ErrCode BitVector_from_Oct(/*@out@*/ wordptr addr, charptr string);
YASM_LIB_DECL
/*@only@*/ charptr BitVector_to_Bin (wordptr addr);
YASM_LIB_DECL
ErrCode BitVector_from_Bin (/*@out@*/ wordptr addr, charptr string);
YASM_LIB_DECL
/*@only@*/ charptr BitVector_to_Dec (wordptr addr);
YASM_LIB_DECL
ErrCode BitVector_from_Dec (/*@out@*/ wordptr addr, charptr string);
typedef struct BitVector_from_Dec_static_data BitVector_from_Dec_static_data;
YASM_LIB_DECL
BitVector_from_Dec_static_data *BitVector_from_Dec_static_Boot(N_word bits);
YASM_LIB_DECL
void BitVector_from_Dec_static_Shutdown(/*@null@*/ BitVector_from_Dec_static_data *data);
YASM_LIB_DECL
ErrCode BitVector_from_Dec_static(BitVector_from_Dec_static_data *data,
/*@out@*/ wordptr addr, charptr string);
YASM_LIB_DECL
/*@only@*/ charptr BitVector_to_Enum (wordptr addr);
YASM_LIB_DECL
ErrCode BitVector_from_Enum (/*@out@*/ wordptr addr, charptr string);
/* ===> bit vector bit operations, functions & tests: */
YASM_LIB_DECL
void BitVector_Bit_Off (/*@out@*/ wordptr addr, N_int indx); /* X = X \ {x} */
YASM_LIB_DECL
void BitVector_Bit_On (/*@out@*/ wordptr addr, N_int indx); /* X = X + {x} */
YASM_LIB_DECL
boolean BitVector_bit_flip (/*@out@*/ wordptr addr, N_int indx); /* (X+{x})\(X*{x}) */
YASM_LIB_DECL
boolean BitVector_bit_test (wordptr addr, N_int indx); /* {x} in X ? */
YASM_LIB_DECL
void BitVector_Bit_Copy (/*@out@*/ wordptr addr, N_int indx, boolean bit);
/* ===> bit vector bit shift & rotate functions: */
YASM_LIB_DECL
void BitVector_LSB (/*@out@*/ wordptr addr, boolean bit);
YASM_LIB_DECL
void BitVector_MSB (/*@out@*/ wordptr addr, boolean bit);
YASM_LIB_DECL
boolean BitVector_lsb_ (wordptr addr);
YASM_LIB_DECL
boolean BitVector_msb_ (wordptr addr);
YASM_LIB_DECL
boolean /*@alt void@*/ BitVector_rotate_left (wordptr addr);
YASM_LIB_DECL
boolean /*@alt void@*/ BitVector_rotate_right (wordptr addr);
YASM_LIB_DECL
boolean /*@alt void@*/ BitVector_shift_left (wordptr addr, boolean carry_in);
YASM_LIB_DECL
boolean /*@alt void@*/ BitVector_shift_right (wordptr addr, boolean carry_in);
YASM_LIB_DECL
void BitVector_Move_Left (wordptr addr, N_int bits);
YASM_LIB_DECL
void BitVector_Move_Right (wordptr addr, N_int bits);
/* ===> bit vector insert/delete bits: */
YASM_LIB_DECL
void BitVector_Insert (wordptr addr, N_int offset, N_int count,
boolean clear);
YASM_LIB_DECL
void BitVector_Delete (wordptr addr, N_int offset, N_int count,
boolean clear);
/* ===> bit vector arithmetic: */
YASM_LIB_DECL
boolean /*@alt void@*/ BitVector_increment (wordptr addr); /* X++ */
YASM_LIB_DECL
boolean /*@alt void@*/ BitVector_decrement (wordptr addr); /* X-- */
YASM_LIB_DECL
boolean /*@alt void@*/ BitVector_compute (wordptr X, wordptr Y, wordptr Z, boolean minus,
boolean *carry);
YASM_LIB_DECL
boolean /*@alt void@*/ BitVector_add (wordptr X, wordptr Y, wordptr Z, boolean *carry);
YASM_LIB_DECL
boolean /*@alt void@*/ BitVector_sub (wordptr X, wordptr Y, wordptr Z, boolean *carry);
YASM_LIB_DECL
boolean /*@alt void@*/ BitVector_inc (wordptr X, wordptr Y);
YASM_LIB_DECL
boolean /*@alt void@*/ BitVector_dec (wordptr X, wordptr Y);
YASM_LIB_DECL
void BitVector_Negate (wordptr X, wordptr Y);
YASM_LIB_DECL
void BitVector_Absolute (wordptr X, wordptr Y);
YASM_LIB_DECL
Z_int BitVector_Sign (wordptr addr);
YASM_LIB_DECL
ErrCode BitVector_Mul_Pos (wordptr X, wordptr Y, wordptr Z, boolean strict);
YASM_LIB_DECL
ErrCode BitVector_Multiply (wordptr X, wordptr Y, wordptr Z);
YASM_LIB_DECL
ErrCode BitVector_Div_Pos (wordptr Q, wordptr X, wordptr Y, wordptr R);
YASM_LIB_DECL
ErrCode BitVector_Divide (wordptr Q, wordptr X, wordptr Y, wordptr R);
YASM_LIB_DECL
ErrCode BitVector_GCD (wordptr X, wordptr Y, wordptr Z);
YASM_LIB_DECL
ErrCode BitVector_GCD2 (wordptr U, wordptr V, wordptr W, /* O */
wordptr X, wordptr Y); /* I */
YASM_LIB_DECL
ErrCode BitVector_Power (wordptr X, wordptr Y, wordptr Z);
/* ===> direct memory access functions: */
YASM_LIB_DECL
void BitVector_Block_Store(wordptr addr, charptr buffer, N_int length);
YASM_LIB_DECL
charptr BitVector_Block_Read (wordptr addr, /*@out@*/ N_intptr length);
/* ===> word array functions: */
YASM_LIB_DECL
void BitVector_Word_Store (wordptr addr, N_int offset, N_int value);
YASM_LIB_DECL
N_int BitVector_Word_Read (wordptr addr, N_int offset);
YASM_LIB_DECL
void BitVector_Word_Insert(wordptr addr, N_int offset, N_int count,
boolean clear);
YASM_LIB_DECL
void BitVector_Word_Delete(wordptr addr, N_int offset, N_int count,
boolean clear);
/* ===> arbitrary size chunk functions: */
YASM_LIB_DECL
void BitVector_Chunk_Store(wordptr addr, N_int chunksize,
N_int offset, N_long value);
YASM_LIB_DECL
N_long BitVector_Chunk_Read (wordptr addr, N_int chunksize,
N_int offset);
/* ===> set operations: */
YASM_LIB_DECL
void Set_Union (wordptr X, wordptr Y, wordptr Z); /* X = Y + Z */
YASM_LIB_DECL
void Set_Intersection (wordptr X, wordptr Y, wordptr Z); /* X = Y * Z */
YASM_LIB_DECL
void Set_Difference (wordptr X, wordptr Y, wordptr Z); /* X = Y \ Z */
YASM_LIB_DECL
void Set_ExclusiveOr (wordptr X, wordptr Y, wordptr Z); /*(Y+Z)\(Y*Z)*/
YASM_LIB_DECL
void Set_Complement (wordptr X, wordptr Y); /* X = ~Y */
/* ===> set functions: */
YASM_LIB_DECL
boolean Set_subset (wordptr X, wordptr Y); /* X in Y ? */
YASM_LIB_DECL
N_int Set_Norm (wordptr addr); /* = | X | */
YASM_LIB_DECL
N_int Set_Norm2 (wordptr addr); /* = | X | */
YASM_LIB_DECL
N_int Set_Norm3 (wordptr addr); /* = | X | */
YASM_LIB_DECL
Z_long Set_Min (wordptr addr); /* = min(X) */
YASM_LIB_DECL
Z_long Set_Max (wordptr addr); /* = max(X) */
/* ===> matrix-of-booleans operations: */
YASM_LIB_DECL
void Matrix_Multiplication(wordptr X, N_int rowsX, N_int colsX,
wordptr Y, N_int rowsY, N_int colsY,
wordptr Z, N_int rowsZ, N_int colsZ);
YASM_LIB_DECL
void Matrix_Product (wordptr X, N_int rowsX, N_int colsX,
wordptr Y, N_int rowsY, N_int colsY,
wordptr Z, N_int rowsZ, N_int colsZ);
YASM_LIB_DECL
void Matrix_Closure (wordptr addr, N_int rows, N_int cols);
YASM_LIB_DECL
void Matrix_Transpose (wordptr X, N_int rowsX, N_int colsX,
wordptr Y, N_int rowsY, N_int colsY);
/*****************************************************************************/
/* VERSION: 6.4 */
/*****************************************************************************/
/* VERSION HISTORY: */
/*****************************************************************************/
/* */
/* Version 6.4 03.10.04 Added C++ comp. directives. Improved "Norm()". */
/* Version 6.3 28.09.02 Added "Create_List()" and "GCD2()". */
/* Version 6.2 15.09.02 Overhauled error handling. Fixed "GCD()". */
/* Version 6.1 08.10.01 Make VMS linker happy: _lsb,_msb => _lsb_,_msb_ */
/* Version 6.0 08.10.00 Corrected overflow handling. */
/* Version 5.8 14.07.00 Added "Power()". Changed "Copy()". */
/* Version 5.7 19.05.99 Quickened "Div_Pos()". Added "Product()". */
/* Version 5.6 02.11.98 Leading zeros eliminated in "to_Hex()". */
/* Version 5.5 21.09.98 Fixed bug of uninitialized "error" in Multiply. */
/* Version 5.4 07.09.98 Fixed bug of uninitialized "error" in Divide. */
/* Version 5.3 12.05.98 Improved Norm. Completed history. */
/* Version 5.2 31.03.98 Improved Norm. */
/* Version 5.1 09.03.98 No changes. */
/* Version 5.0 01.03.98 Major additions and rewrite. */
/* Version 4.2 16.07.97 Added is_empty, is_full. */
/* Version 4.1 30.06.97 Added word-ins/del, move-left/right, inc/dec. */
/* Version 4.0 23.04.97 Rewrite. Added bit shift and bool. matrix ops. */
/* Version 3.2 04.02.97 Added interval methods. */
/* Version 3.1 21.01.97 Fixed bug on 64 bit machines. */
/* Version 3.0 12.01.97 Added flip. */
/* Version 2.0 14.12.96 Efficiency and consistency improvements. */
/* Version 1.1 08.01.96 Added Resize and ExclusiveOr. */
/* Version 1.0 14.12.95 First version under UNIX (with Perl module). */
/* Version 0.9 01.11.93 First version of C library under MS-DOS. */
/* Version 0.1 ??.??.89 First version in Turbo Pascal under CP/M. */
/* */
/*****************************************************************************/
/* AUTHOR: */
/*****************************************************************************/
/* */
/* Steffen Beyer */
/* mailto:sb@engelschall.com */
/* http://www.engelschall.com/u/sb/download/ */
/* */
/*****************************************************************************/
/* COPYRIGHT: */
/*****************************************************************************/
/* */
/* Copyright (c) 1995 - 2004 by Steffen Beyer. */
/* All rights reserved. */
/* */
/*****************************************************************************/
/* LICENSE: */
/*****************************************************************************/
/* This package is free software; you can use, modify and redistribute */
/* it under the same terms as Perl itself, i.e., under the terms of */
/* the "Artistic License" or the "GNU General Public License". */
/* */
/* The C library at the core of this Perl module can additionally */
/* be used, modified and redistributed under the terms of the */
/* "GNU Library General Public License". */
/* */
/*****************************************************************************/
/* ARTISTIC LICENSE: */
/*****************************************************************************/
/*
The "Artistic License"
Preamble
The intent of this document is to state the conditions under which a
Package may be copied, such that the Copyright Holder maintains some
semblance of artistic control over the development of the package,
while giving the users of the package the right to use and distribute
the Package in a more-or-less customary fashion, plus the right to make
reasonable modifications.
Definitions:
"Package" refers to the collection of files distributed by the
Copyright Holder, and derivatives of that collection of files
created through textual modification.
"Standard Version" refers to such a Package if it has not been
modified, or has been modified in accordance with the wishes
of the Copyright Holder as specified below.
"Copyright Holder" is whoever is named in the copyright or
copyrights for the package.
"You" is you, if you're thinking about copying or distributing
this Package.
"Reasonable copying fee" is whatever you can justify on the
basis of media cost, duplication charges, time of people involved,
and so on. (You will not be required to justify it to the
Copyright Holder, but only to the computing community at large
as a market that must bear the fee.)
"Freely Available" means that no fee is charged for the item
itself, though there may be fees involved in handling the item.
It also means that recipients of the item may redistribute it
under the same conditions they received it.
1. You may make and give away verbatim copies of the source form of the
Standard Version of this Package without restriction, provided that you
duplicate all of the original copyright notices and associated disclaimers.
2. You may apply bug fixes, portability fixes and other modifications
derived from the Public Domain or from the Copyright Holder. A Package
modified in such a way shall still be considered the Standard Version.
3. You may otherwise modify your copy of this Package in any way, provided
that you insert a prominent notice in each changed file stating how and
when you changed that file, and provided that you do at least ONE of the
following:
a) place your modifications in the Public Domain or otherwise make them
Freely Available, such as by posting said modifications to Usenet or
an equivalent medium, or placing the modifications on a major archive
site such as uunet.uu.net, or by allowing the Copyright Holder to include
your modifications in the Standard Version of the Package.
b) use the modified Package only within your corporation or organization.
c) rename any non-standard executables so the names do not conflict
with standard executables, which must also be provided, and provide
a separate manual page for each non-standard executable that clearly
documents how it differs from the Standard Version.
d) make other distribution arrangements with the Copyright Holder.
4. You may distribute the programs of this Package in object code or
executable form, provided that you do at least ONE of the following:
a) distribute a Standard Version of the executables and library files,
together with instructions (in the manual page or equivalent) on where
to get the Standard Version.
b) accompany the distribution with the machine-readable source of
the Package with your modifications.
c) give non-standard executables non-standard names, and clearly
document the differences in manual pages (or equivalent), together
with instructions on where to get the Standard Version.
d) make other distribution arrangements with the Copyright Holder.
5. You may charge a reasonable copying fee for any distribution of this
Package. You may charge any fee you choose for support of this
Package. You may not charge a fee for this Package itself. However,
you may distribute this Package in aggregate with other (possibly
commercial) programs as part of a larger (possibly commercial) software
distribution provided that you do not advertise this Package as a
product of your own. You may embed this Package's interpreter within
an executable of yours (by linking); this shall be construed as a mere
form of aggregation, provided that the complete Standard Version of the
interpreter is so embedded.
6. The scripts and library files supplied as input to or produced as
output from the programs of this Package do not automatically fall
under the copyright of this Package, but belong to whoever generated
them, and may be sold commercially, and may be aggregated with this
Package. If such scripts or library files are aggregated with this
Package via the so-called "undump" or "unexec" methods of producing a
binary executable image, then distribution of such an image shall
neither be construed as a distribution of this Package nor shall it
fall under the restrictions of Paragraphs 3 and 4, provided that you do
not represent such an executable image as a Standard Version of this
Package.
7. C subroutines (or comparably compiled subroutines in other
languages) supplied by you and linked into this Package in order to
emulate subroutines and variables of the language defined by this
Package shall not be considered part of this Package, but are the
equivalent of input as in Paragraph 6, provided these subroutines do
not change the language in any way that would cause it to fail the
regression tests for the language.
8. Aggregation of this Package with a commercial distribution is always
permitted provided that the use of this Package is embedded; that is,
when no overt attempt is made to make this Package's interfaces visible
to the end user of the commercial distribution. Such use shall not be
construed as a distribution of this Package.
9. The name of the Copyright Holder may not be used to endorse or promote
products derived from this software without specific prior written permission.
10. THIS PACKAGE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR
IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.
The End
*/
/*****************************************************************************/
/* GNU GENERAL PUBLIC LICENSE: */
/*****************************************************************************/
/* This program is free software; you can redistribute it and/or */
/* modify it under the terms of the GNU General Public License */
/* as published by the Free Software Foundation; either version 2 */
/* of the License, or (at your option) any later version. */
/* */
/* This program is distributed in the hope that it will be useful, */
/* but WITHOUT ANY WARRANTY; without even the implied warranty of */
/* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the */
/* GNU General Public License for more details. */
/* */
/* You should have received a copy of the GNU General Public License */
/* along with this program; if not, write to the */
/* Free Software Foundation, Inc., */
/* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */
/* */
/*****************************************************************************/
/* GNU LIBRARY GENERAL PUBLIC LICENSE: */
/*****************************************************************************/
/* */
/* This library is free software; you can redistribute it and/or */
/* modify it under the terms of the GNU Library General Public */
/* License as published by the Free Software Foundation; either */
/* version 2 of the License, or (at your option) any later version. */
/* */
/* This library is distributed in the hope that it will be useful, */
/* but WITHOUT ANY WARRANTY; without even the implied warranty of */
/* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU */
/* Library General Public License for more details. */
/* */
/* You should have received a copy of the GNU Library General Public */
/* License along with this library; if not, write to the */
/* Free Software Foundation, Inc., */
/* 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */
/* */
/* or download a copy from ftp://ftp.gnu.org/pub/gnu/COPYING.LIB-2.0 */
/* */
/*****************************************************************************/
#endif

View File

@ -0,0 +1,638 @@
/**
* \file libyasm/bytecode.h
* \brief YASM bytecode interface.
*
* \license
* Copyright (C) 2001-2007 Peter Johnson
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* - Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* - Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND OTHER CONTRIBUTORS ``AS IS''
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR OTHER CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
* \endlicense
*/
#ifndef YASM_BYTECODE_H
#define YASM_BYTECODE_H
#ifndef YASM_LIB_DECL
#define YASM_LIB_DECL
#endif
/** A data value (opaque type). */
typedef struct yasm_dataval yasm_dataval;
/** A list of data values. */
typedef struct yasm_datavalhead yasm_datavalhead;
/** Linked list of data values. */
/*@reldef@*/ STAILQ_HEAD(yasm_datavalhead, yasm_dataval);
/** Add a dependent span for a bytecode.
* \param add_span_data add_span_data passed into bc_calc_len()
* \param bc bytecode containing span
* \param id non-zero identifier for span; may be any non-zero value
* if <0, expand is called for any change;
* if >0, expand is only called when exceeds threshold
* \param value dependent value for bytecode expansion
* \param neg_thres negative threshold for long/short decision
* \param pos_thres positive threshold for long/short decision
*/
typedef void (*yasm_bc_add_span_func)
(void *add_span_data, yasm_bytecode *bc, int id, const yasm_value *value,
long neg_thres, long pos_thres);
/** Bytecode callback structure. Any implementation of a specific bytecode
* must implement these functions and this callback structure. The bytecode
* implementation-specific data is stored in #yasm_bytecode.contents.
*/
typedef struct yasm_bytecode_callback {
/** Destroys the implementation-specific data.
* Called from yasm_bc_destroy().
* \param contents #yasm_bytecode.contents
*/
void (*destroy) (/*@only@*/ void *contents);
/** Prints the implementation-specific data (for debugging purposes).
* Called from yasm_bc_print().
* \param contents #yasm_bytecode.contents
* \param f file
* \param indent_level indentation level
*/
void (*print) (const void *contents, FILE *f, int indent_level);
/** Finalizes the bytecode after parsing. Called from yasm_bc_finalize().
* A generic fill-in for this is yasm_bc_finalize_common().
* \param bc bytecode
* \param prev_bc bytecode directly preceding bc
*/
void (*finalize) (yasm_bytecode *bc, yasm_bytecode *prev_bc);
/** Return elements size of a data bytecode.
* This function should return the size of each elements of a data
* bytecode, for proper dereference of symbols attached to it.
* \param bc bytecode
* \return 0 if element size is unknown.
*/
int (*elem_size) (yasm_bytecode *bc);
/** Calculates the minimum size of a bytecode.
* Called from yasm_bc_calc_len().
* A generic fill-in for this is yasm_bc_calc_len_common(), but as this
* function internal errors when called, be very careful when using it!
* This function should simply add to bc->len and not set it directly
* (it's initialized by yasm_bc_calc_len() prior to passing control to
* this function).
*
* \param bc bytecode
* \param add_span function to call to add a span
* \param add_span_data extra data to be passed to add_span function
* \return 0 if no error occurred, nonzero if there was an error
* recognized (and output) during execution.
* \note May store to bytecode updated expressions.
*/
int (*calc_len) (yasm_bytecode *bc, yasm_bc_add_span_func add_span,
void *add_span_data);
/** Recalculates the bytecode's length based on an expanded span length.
* Called from yasm_bc_expand().
* A generic fill-in for this is yasm_bc_expand_common(), but as this
* function internal errors when called, if used, ensure that calc_len()
* never adds a span.
* This function should simply add to bc->len to increase the length by
* a delta amount.
* \param bc bytecode
* \param span span ID (as given to add_span in calc_len)
* \param old_val previous span value
* \param new_val new span value
* \param neg_thres negative threshold for long/short decision
* (returned)
* \param pos_thres positive threshold for long/short decision
* (returned)
* \return 0 if bc no longer dependent on this span's length, negative if
* there was an error recognized (and output) during execution,
* and positive if bc size may increase for this span further
* based on the new negative and positive thresholds returned.
* \note May store to bytecode updated expressions.
*/
int (*expand) (yasm_bytecode *bc, int span, long old_val, long new_val,
/*@out@*/ long *neg_thres, /*@out@*/ long *pos_thres);
/** Convert a bytecode into its byte representation.
* Called from yasm_bc_tobytes().
* A generic fill-in for this is yasm_bc_tobytes_common(), but as this
* function internal errors when called, be very careful when using it!
* \param bc bytecode
* \param bufp byte representation destination buffer;
* should be incremented as it's written to,
* so that on return its delta from the
* passed-in buf matches the bytecode length
* (it's okay not to do this if an error
* indication is returned)
* \param bufstart For calculating the correct offset parameter for
* the \a output_value calls: *bufp - bufstart.
* \param d data to pass to each call to
* output_value/output_reloc
* \param output_value function to call to convert values into their byte
* representation
* \param output_reloc function to call to output relocation entries
* for a single sym
* \return Nonzero on error, 0 on success.
* \note May result in non-reversible changes to the bytecode, but it's
* preferable if calling this function twice would result in the
* same output.
*/
int (*tobytes) (yasm_bytecode *bc, unsigned char **bufp,
unsigned char *bufstart, void *d,
yasm_output_value_func output_value,
/*@null@*/ yasm_output_reloc_func output_reloc);
/** Special bytecode classifications. Most bytecode types should use
* #YASM_BC_SPECIAL_NONE. Others cause special handling to kick in
* in various parts of yasm.
*/
enum yasm_bytecode_special_type {
YASM_BC_SPECIAL_NONE = 0,
/** Bytecode reserves space instead of outputting data. */
YASM_BC_SPECIAL_RESERVE,
/** Adjusts offset instead of calculating len. */
YASM_BC_SPECIAL_OFFSET,
/** Instruction bytecode. */
YASM_BC_SPECIAL_INSN
} special;
} yasm_bytecode_callback;
/** A bytecode. */
struct yasm_bytecode {
/** Bytecodes are stored as a singly linked list, with tail insertion.
* \see section.h (#yasm_section).
*/
/*@reldef@*/ STAILQ_ENTRY(yasm_bytecode) link;
/** The bytecode callback structure for this bytecode. May be NULL
* during partial initialization.
*/
/*@null@*/ const yasm_bytecode_callback *callback;
/** Pointer to section containing bytecode; NULL if not part of a
* section.
*/
/*@dependent@*/ /*@null@*/ yasm_section *section;
/** Number of times bytecode is repeated.
* NULL=1 (to save space in the common case).
*/
/*@only@*/ /*@null@*/ yasm_expr *multiple;
/** Total length of entire bytecode (not including multiple copies). */
unsigned long len;
/** Number of copies, integer version. */
long mult_int;
/** Line number where bytecode was defined. */
unsigned long line;
/** Offset of bytecode from beginning of its section.
* 0-based, ~0UL (e.g. all 1 bits) if unknown.
*/
unsigned long offset;
/** Unique integer index of bytecode. Used during optimization. */
unsigned long bc_index;
/** NULL-terminated array of labels that point to this bytecode (as the
* bytecode previous to the label). NULL if no labels point here.
*/
/*@null@*/ yasm_symrec **symrecs;
/** Implementation-specific data (type identified by callback). */
void *contents;
};
/** Create a bytecode of any specified type.
* \param callback bytecode callback functions, if NULL, creates empty
* bytecode (may not be resolved or output)
* \param contents type-specific data
* \param line virtual line (from yasm_linemap)
* \return Newly allocated bytecode of the specified type.
*/
YASM_LIB_DECL
/*@only@*/ yasm_bytecode *yasm_bc_create_common
(/*@null@*/ const yasm_bytecode_callback *callback,
/*@only@*/ /*@null@*/ void *contents, unsigned long line);
/** Transform a bytecode of any type into a different type.
* \param bc bytecode to transform
* \param callback new bytecode callback function
* \param contents new type-specific data
*/
YASM_LIB_DECL
void yasm_bc_transform(yasm_bytecode *bc,
const yasm_bytecode_callback *callback,
void *contents);
/** Common bytecode callback finalize function, for where no finalization
* is ever required for this type of bytecode.
*/
YASM_LIB_DECL
void yasm_bc_finalize_common(yasm_bytecode *bc, yasm_bytecode *prev_bc);
/** Common bytecode callback calc_len function, for where the bytecode has
* no calculatable length. Causes an internal error if called.
*/
YASM_LIB_DECL
int yasm_bc_calc_len_common(yasm_bytecode *bc, yasm_bc_add_span_func add_span,
void *add_span_data);
/** Common bytecode callback expand function, for where the bytecode is
* always short (calc_len never calls add_span). Causes an internal
* error if called.
*/
YASM_LIB_DECL
int yasm_bc_expand_common
(yasm_bytecode *bc, int span, long old_val, long new_val,
/*@out@*/ long *neg_thres, /*@out@*/ long *pos_thres);
/** Common bytecode callback tobytes function, for where the bytecode
* cannot be converted to bytes. Causes an internal error if called.
*/
YASM_LIB_DECL
int yasm_bc_tobytes_common
(yasm_bytecode *bc, unsigned char **bufp, unsigned char *bufstart, void *d,
yasm_output_value_func output_value,
/*@null@*/ yasm_output_reloc_func output_reloc);
/** Get the next bytecode in a linked list of bytecodes.
* \param bc bytecode
* \return Next bytecode.
*/
#define yasm_bc__next(bc) STAILQ_NEXT(bc, link)
/** Set multiple field of a bytecode.
* A bytecode can be repeated a number of times when output. This function
* sets that multiple.
* \param bc bytecode
* \param e multiple (kept, do not free)
*/
YASM_LIB_DECL
void yasm_bc_set_multiple(yasm_bytecode *bc, /*@keep@*/ yasm_expr *e);
/** Create a bytecode containing data value(s).
* \param datahead list of data values (kept, do not free)
* \param size storage size (in bytes) for each data value
* \param append_zero append a single zero byte after each data value
* (if non-zero)
* \param arch architecture (optional); if provided, data items
* are directly simplified to bytes if possible
* \param line virtual line (from yasm_linemap)
* \return Newly allocated bytecode.
*/
YASM_LIB_DECL
/*@only@*/ yasm_bytecode *yasm_bc_create_data
(yasm_datavalhead *datahead, unsigned int size, int append_zero,
/*@null@*/ yasm_arch *arch, unsigned long line);
/** Create a bytecode containing LEB128-encoded data value(s).
* \param datahead list of data values (kept, do not free)
* \param sign signedness (1=signed, 0=unsigned) of each data value
* \param line virtual line (from yasm_linemap)
* \return Newly allocated bytecode.
*/
YASM_LIB_DECL
/*@only@*/ yasm_bytecode *yasm_bc_create_leb128
(yasm_datavalhead *datahead, int sign, unsigned long line);
/** Create a bytecode reserving space.
* \param numitems number of reserve "items" (kept, do not free)
* \param itemsize reserved size (in bytes) for each item
* \param line virtual line (from yasm_linemap)
* \return Newly allocated bytecode.
*/
YASM_LIB_DECL
/*@only@*/ yasm_bytecode *yasm_bc_create_reserve
(/*@only@*/ yasm_expr *numitems, unsigned int itemsize,
unsigned long line);
/** Get the number of items and itemsize for a reserve bytecode. If bc
* is not a reserve bytecode, returns NULL.
* \param bc bytecode
* \param itemsize reserved size (in bytes) for each item (returned)
* \return NULL if bc is not a reserve bytecode, otherwise an expression
* for the number of items to reserve.
*/
YASM_LIB_DECL
/*@null@*/ const yasm_expr *yasm_bc_reserve_numitems
(yasm_bytecode *bc, /*@out@*/ unsigned int *itemsize);
/** Create a bytecode that includes a binary file verbatim.
* \param filename path to binary file (kept, do not free)
* \param start starting location in file (in bytes) to read data from
* (kept, do not free); may be NULL to indicate 0
* \param maxlen maximum number of bytes to read from the file (kept, do
* do not free); may be NULL to indicate no maximum
* \param linemap line mapping repository
* \param line virtual line (from yasm_linemap) for the bytecode
* \return Newly allocated bytecode.
*/
YASM_LIB_DECL
/*@only@*/ yasm_bytecode *yasm_bc_create_incbin
(/*@only@*/ char *filename, /*@only@*/ /*@null@*/ yasm_expr *start,
/*@only@*/ /*@null@*/ yasm_expr *maxlen, yasm_linemap *linemap,
unsigned long line);
/** Create a bytecode that aligns the following bytecode to a boundary.
* \param boundary byte alignment (must be a power of two)
* \param fill fill data (if NULL, code_fill or 0 is used)
* \param maxskip maximum number of bytes to skip
* \param code_fill code fill data (if NULL, 0 is used)
* \param line virtual line (from yasm_linemap)
* \return Newly allocated bytecode.
* \note The precedence on generated fill is as follows:
* - from fill parameter (if not NULL)
* - from code_fill parameter (if not NULL)
* - 0
*/
YASM_LIB_DECL
/*@only@*/ yasm_bytecode *yasm_bc_create_align
(/*@keep@*/ yasm_expr *boundary, /*@keep@*/ /*@null@*/ yasm_expr *fill,
/*@keep@*/ /*@null@*/ yasm_expr *maxskip,
/*@null@*/ const unsigned char **code_fill, unsigned long line);
/** Create a bytecode that puts the following bytecode at a fixed section
* offset.
* \param start section offset of following bytecode
* \param fill fill value
* \param line virtual line (from yasm_linemap)
* \return Newly allocated bytecode.
*/
YASM_LIB_DECL
/*@only@*/ yasm_bytecode *yasm_bc_create_org
(unsigned long start, unsigned long fill, unsigned long line);
/** Get the section that contains a particular bytecode.
* \param bc bytecode
* \return Section containing bc (can be NULL if bytecode is not part of a
* section).
*/
YASM_LIB_DECL
/*@dependent@*/ /*@null@*/ yasm_section *yasm_bc_get_section
(yasm_bytecode *bc);
/** Add to the list of symrecs that reference a bytecode. For symrec use
* only.
* \param bc bytecode
* \param sym symbol
*/
YASM_LIB_DECL
void yasm_bc__add_symrec(yasm_bytecode *bc, /*@dependent@*/ yasm_symrec *sym);
/** Delete (free allocated memory for) a bytecode.
* \param bc bytecode (only pointer to it); may be NULL
*/
YASM_LIB_DECL
void yasm_bc_destroy(/*@only@*/ /*@null@*/ yasm_bytecode *bc);
/** Print a bytecode. For debugging purposes.
* \param f file
* \param indent_level indentation level
* \param bc bytecode
*/
YASM_LIB_DECL
void yasm_bc_print(const yasm_bytecode *bc, FILE *f, int indent_level);
/** Finalize a bytecode after parsing.
* \param bc bytecode
* \param prev_bc bytecode directly preceding bc in a list of bytecodes
*/
YASM_LIB_DECL
void yasm_bc_finalize(yasm_bytecode *bc, yasm_bytecode *prev_bc);
/** Determine the distance between the starting offsets of two bytecodes.
* \param precbc1 preceding bytecode to the first bytecode
* \param precbc2 preceding bytecode to the second bytecode
* \return Distance in bytes between the two bytecodes (bc2-bc1), or NULL if
* the distance was indeterminate.
* \warning Only valid /after/ optimization.
*/
YASM_LIB_DECL
/*@null@*/ /*@only@*/ yasm_intnum *yasm_calc_bc_dist
(yasm_bytecode *precbc1, yasm_bytecode *precbc2);
/** Get the offset of the next bytecode (the next bytecode doesn't have to
* actually exist).
* \param precbc preceding bytecode
* \return Offset of the next bytecode in bytes.
* \warning Only valid /after/ optimization.
*/
YASM_LIB_DECL
unsigned long yasm_bc_next_offset(yasm_bytecode *precbc);
/** Return elemens size of a data bytecode.
* Returns the size of each elements of a data bytecode, for proper dereference
* of symbols attached to it.
* \param bc bytecode
* \return 0 if element size is unknown
*/
YASM_LIB_DECL
int yasm_bc_elem_size(yasm_bytecode *bc);
/** Resolve EQUs in a bytecode and calculate its minimum size.
* Generates dependent bytecode spans for cases where, if the length spanned
* increases, it could cause the bytecode size to increase.
* Any bytecode multiple is NOT included in the length or spans generation;
* this must be handled at a higher level.
* \param bc bytecode
* \param add_span function to call to add a span
* \param add_span_data extra data to be passed to add_span function
* \return 0 if no error occurred, nonzero if there was an error recognized
* (and output) during execution.
* \note May store to bytecode updated expressions and the short length.
*/
YASM_LIB_DECL
int yasm_bc_calc_len(yasm_bytecode *bc, yasm_bc_add_span_func add_span,
void *add_span_data);
/** Recalculate a bytecode's length based on an expanded span length.
* \param bc bytecode
* \param span span ID (as given to yasm_bc_add_span_func in
* yasm_bc_calc_len)
* \param old_val previous span value
* \param new_val new span value
* \param neg_thres negative threshold for long/short decision (returned)
* \param pos_thres positive threshold for long/short decision (returned)
* \return 0 if bc no longer dependent on this span's length, negative if
* there was an error recognized (and output) during execution, and
* positive if bc size may increase for this span further based on the
* new negative and positive thresholds returned.
* \note May store to bytecode updated expressions and the updated length.
*/
YASM_LIB_DECL
int yasm_bc_expand(yasm_bytecode *bc, int span, long old_val, long new_val,
/*@out@*/ long *neg_thres, /*@out@*/ long *pos_thres);
/** Convert a bytecode into its byte representation.
* \param bc bytecode
* \param buf byte representation destination buffer
* \param bufsize size of buf (in bytes) prior to call; size of the
* generated data after call
* \param gap if nonzero, indicates the data does not really need to
* exist in the object file; if nonzero, contents of buf
* are undefined [output]
* \param d data to pass to each call to output_value/output_reloc
* \param output_value function to call to convert values into their byte
* representation
* \param output_reloc function to call to output relocation entries
* for a single sym
* \return Newly allocated buffer that should be used instead of buf for
* reading the byte representation, or NULL if buf was big enough to
* hold the entire byte representation.
* \note Calling twice on the same bytecode may \em not produce the same
* results on the second call, as calling this function may result in
* non-reversible changes to the bytecode.
*/
YASM_LIB_DECL
/*@null@*/ /*@only@*/ unsigned char *yasm_bc_tobytes
(yasm_bytecode *bc, unsigned char *buf, unsigned long *bufsize,
/*@out@*/ int *gap, void *d, yasm_output_value_func output_value,
/*@null@*/ yasm_output_reloc_func output_reloc)
/*@sets *buf@*/;
/** Get the bytecode multiple value as an integer.
* \param bc bytecode
* \param multiple multiple value (output)
* \param calc_bc_dist nonzero if distances between bytecodes should be
* calculated, 0 if error should be returned in this case
* \return 1 on error (set with yasm_error_set), 0 on success.
*/
YASM_LIB_DECL
int yasm_bc_get_multiple(yasm_bytecode *bc, /*@out@*/ long *multiple,
int calc_bc_dist);
/** Get the bytecode multiple value as an expression.
* \param bc bytecode
* \return Bytecode multiple, NULL if =1.
*/
YASM_LIB_DECL
const yasm_expr *yasm_bc_get_multiple_expr(const yasm_bytecode *bc);
/** Get a #yasm_insn structure from an instruction bytecode (if possible).
* \param bc bytecode
* \return Instruction details if bytecode is an instruction bytecode,
* otherwise NULL.
*/
YASM_LIB_DECL
/*@dependent@*/ /*@null@*/ yasm_insn *yasm_bc_get_insn(yasm_bytecode *bc);
/** Create a new data value from an expression.
* \param expn expression
* \return Newly allocated data value.
*/
YASM_LIB_DECL
yasm_dataval *yasm_dv_create_expr(/*@keep@*/ yasm_expr *expn);
/** Create a new data value from a string.
* \param contents string (may contain NULs)
* \param len length of string
* \return Newly allocated data value.
*/
YASM_LIB_DECL
yasm_dataval *yasm_dv_create_string(/*@keep@*/ char *contents, size_t len);
/** Create a new data value from raw bytes data.
* \param contents raw data (may contain NULs)
* \param len length
* \return Newly allocated data value.
*/
YASM_LIB_DECL
yasm_dataval *yasm_dv_create_raw(/*@keep@*/ unsigned char *contents,
unsigned long len);
/** Create a new uninitialized data value.
* \return Newly allocated data value.
*/
YASM_LIB_DECL
yasm_dataval *yasm_dv_create_reserve(void);
#ifndef YASM_DOXYGEN
#define yasm_dv_create_string(s, l) yasm_dv_create_raw((unsigned char *)(s), \
(unsigned long)(l))
#endif
/** Get the underlying value of a data value.
* \param dv data value
* \return Value, or null if non-value (e.g. string or raw).
*/
YASM_LIB_DECL
yasm_value *yasm_dv_get_value(yasm_dataval *dv);
/** Set multiple field of a data value.
* A data value can be repeated a number of times when output. This function
* sets that multiple.
* \param dv data value
* \param e multiple (kept, do not free)
*/
YASM_LIB_DECL
void yasm_dv_set_multiple(yasm_dataval *dv, /*@keep@*/ yasm_expr *e);
/** Get the data value multiple value as an unsigned long integer.
* \param dv data value
* \param multiple multiple value (output)
* \return 1 on error (set with yasm_error_set), 0 on success.
*/
YASM_LIB_DECL
int yasm_dv_get_multiple(yasm_dataval *dv, /*@out@*/ unsigned long *multiple);
/** Initialize a list of data values.
* \param headp list of data values
*/
void yasm_dvs_initialize(yasm_datavalhead *headp);
#ifndef YASM_DOXYGEN
#define yasm_dvs_initialize(headp) STAILQ_INIT(headp)
#endif
/** Delete (free allocated memory for) a list of data values.
* \param headp list of data values
*/
YASM_LIB_DECL
void yasm_dvs_delete(yasm_datavalhead *headp);
/** Add data value to the end of a list of data values.
* \note Does not make a copy of the data value; so don't pass this function
* static or local variables, and discard the dv pointer after calling
* this function.
* \param headp data value list
* \param dv data value (may be NULL)
* \return If data value was actually appended (it wasn't NULL), the data
* value; otherwise NULL.
*/
YASM_LIB_DECL
/*@null@*/ yasm_dataval *yasm_dvs_append
(yasm_datavalhead *headp, /*@returned@*/ /*@null@*/ yasm_dataval *dv);
/** Print a data value list. For debugging purposes.
* \param f file
* \param indent_level indentation level
* \param headp data value list
*/
YASM_LIB_DECL
void yasm_dvs_print(const yasm_datavalhead *headp, FILE *f, int indent_level);
#endif

View File

@ -0,0 +1,456 @@
/*
* <sys/queue.h> implementation for systems that don't have it.
*
* Copyright (c) 1991, 1993
* The Regents of the University of California. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. Neither the name of the University nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*
* @(#)queue.h 8.5 (Berkeley) 8/20/94
* $FreeBSD: src/sys/sys/queue.h,v 1.32.2.4 2001/03/31 03:33:39 hsu Exp $
*/
#ifndef SYS_QUEUE_H
#define SYS_QUEUE_H
/*
* This file defines four types of data structures: singly-linked lists,
* singly-linked tail queues, lists and tail queues.
*
* A singly-linked list is headed by a single forward pointer. The elements
* are singly linked for minimum space and pointer manipulation overhead at
* the expense of O(n) removal for arbitrary elements. New elements can be
* added to the list after an existing element or at the head of the list.
* Elements being removed from the head of the list should use the explicit
* macro for this purpose for optimum efficiency. A singly-linked list may
* only be traversed in the forward direction. Singly-linked lists are ideal
* for applications with large datasets and few or no removals or for
* implementing a LIFO queue.
*
* A singly-linked tail queue is headed by a pair of pointers, one to the
* head of the list and the other to the tail of the list. The elements are
* singly linked for minimum space and pointer manipulation overhead at the
* expense of O(n) removal for arbitrary elements. New elements can be added
* to the list after an existing element, at the head of the list, or at the
* end of the list. Elements being removed from the head of the tail queue
* should use the explicit macro for this purpose for optimum efficiency.
* A singly-linked tail queue may only be traversed in the forward direction.
* Singly-linked tail queues are ideal for applications with large datasets
* and few or no removals or for implementing a FIFO queue.
*
* A list is headed by a single forward pointer (or an array of forward
* pointers for a hash table header). The elements are doubly linked
* so that an arbitrary element can be removed without a need to
* traverse the list. New elements can be added to the list before
* or after an existing element or at the head of the list. A list
* may only be traversed in the forward direction.
*
* A tail queue is headed by a pair of pointers, one to the head of the
* list and the other to the tail of the list. The elements are doubly
* linked so that an arbitrary element can be removed without a need to
* traverse the list. New elements can be added to the list before or
* after an existing element, at the head of the list, or at the end of
* the list. A tail queue may be traversed in either direction.
*
* For details on the use of these macros, see the queue(3) manual page.
*
*
* SLIST LIST STAILQ TAILQ
* _HEAD + + + +
* _HEAD_INITIALIZER + + + +
* _ENTRY + + + +
* _INIT + + + +
* _EMPTY + + + +
* _FIRST + + + +
* _NEXT + + + +
* _PREV - - - +
* _LAST - - + +
* _FOREACH + + + +
* _FOREACH_SAFE + + + +
* _FOREACH_REVERSE - - - +
* _FOREACH_REVERSE_SAFE - - - +
* _INSERT_HEAD + + + +
* _INSERT_BEFORE - + - +
* _INSERT_AFTER + + + +
* _INSERT_TAIL - - + +
* _CONCAT - - + +
* _REMOVE_HEAD + - + -
* _REMOVE + + + +
*
*/
/*
* Singly-linked List declarations.
*/
#define SLIST_HEAD(name, type) \
struct name { \
struct type *slh_first; /* first element */ \
}
#define SLIST_HEAD_INITIALIZER(head) \
{ NULL }
#define SLIST_ENTRY(type) \
struct { \
struct type *sle_next; /* next element */ \
}
/*
* Singly-linked List functions.
*/
#define SLIST_EMPTY(head) ((head)->slh_first == NULL)
#define SLIST_FIRST(head) ((head)->slh_first)
#define SLIST_FOREACH(var, head, field) \
for ((var) = SLIST_FIRST((head)); \
(var); \
(var) = SLIST_NEXT((var), field))
#define SLIST_FOREACH_SAFE(var, head, field, tvar) \
for ((var) = SLIST_FIRST((head)); \
(var) && ((tvar) = SLIST_NEXT((var), field), 1); \
(var) = (tvar))
#define SLIST_FOREACH_PREVPTR(var, varp, head, field) \
for ((varp) = &SLIST_FIRST((head)); \
((var) = *(varp)) != NULL; \
(varp) = &SLIST_NEXT((var), field))
#define SLIST_INIT(head) do { \
SLIST_FIRST((head)) = NULL; \
} while (0)
#define SLIST_INSERT_AFTER(slistelm, elm, field) do { \
SLIST_NEXT((elm), field) = SLIST_NEXT((slistelm), field); \
SLIST_NEXT((slistelm), field) = (elm); \
} while (0)
#define SLIST_INSERT_HEAD(head, elm, field) do { \
SLIST_NEXT((elm), field) = SLIST_FIRST((head)); \
SLIST_FIRST((head)) = (elm); \
} while (0)
#define SLIST_NEXT(elm, field) ((elm)->field.sle_next)
#define SLIST_REMOVE(head, elm, type, field) do { \
if (SLIST_FIRST((head)) == (elm)) { \
SLIST_REMOVE_HEAD((head), field); \
} \
else { \
struct type *curelm = SLIST_FIRST((head)); \
while (SLIST_NEXT(curelm, field) != (elm)) \
curelm = SLIST_NEXT(curelm, field); \
SLIST_NEXT(curelm, field) = \
SLIST_NEXT(SLIST_NEXT(curelm, field), field); \
} \
} while (0)
#define SLIST_REMOVE_HEAD(head, field) do { \
SLIST_FIRST((head)) = SLIST_NEXT(SLIST_FIRST((head)), field); \
} while (0)
/*
* Singly-linked Tail queue declarations.
*/
#define STAILQ_HEAD(name, type) \
struct name { \
struct type *stqh_first;/* first element */ \
struct type **stqh_last;/* addr of last next element */ \
}
#define STAILQ_HEAD_INITIALIZER(head) \
{ NULL, &(head).stqh_first }
#define STAILQ_ENTRY(type) \
struct { \
struct type *stqe_next; /* next element */ \
}
/*
* Singly-linked Tail queue functions.
*/
#define STAILQ_CONCAT(head1, head2) do { \
if (!STAILQ_EMPTY((head2))) { \
*(head1)->stqh_last = (head2)->stqh_first; \
(head1)->stqh_last = (head2)->stqh_last; \
STAILQ_INIT((head2)); \
} \
} while (0)
#define STAILQ_EMPTY(head) ((head)->stqh_first == NULL)
#define STAILQ_FIRST(head) ((head)->stqh_first)
#define STAILQ_FOREACH(var, head, field) \
for((var) = STAILQ_FIRST((head)); \
(var); \
(var) = STAILQ_NEXT((var), field))
#define STAILQ_FOREACH_SAFE(var, head, field, tvar) \
for ((var) = STAILQ_FIRST((head)); \
(var) && ((tvar) = STAILQ_NEXT((var), field), 1); \
(var) = (tvar))
#define STAILQ_INIT(head) do { \
STAILQ_FIRST((head)) = NULL; \
(head)->stqh_last = &STAILQ_FIRST((head)); \
} while (0)
#define STAILQ_INSERT_AFTER(head, tqelm, elm, field) do { \
if ((STAILQ_NEXT((elm), field) = STAILQ_NEXT((tqelm), field)) == NULL)\
(head)->stqh_last = &STAILQ_NEXT((elm), field); \
STAILQ_NEXT((tqelm), field) = (elm); \
} while (0)
#define STAILQ_INSERT_HEAD(head, elm, field) do { \
if ((STAILQ_NEXT((elm), field) = STAILQ_FIRST((head))) == NULL) \
(head)->stqh_last = &STAILQ_NEXT((elm), field); \
STAILQ_FIRST((head)) = (elm); \
} while (0)
#define STAILQ_INSERT_TAIL(head, elm, field) do { \
STAILQ_NEXT((elm), field) = NULL; \
*(head)->stqh_last = (elm); \
(head)->stqh_last = &STAILQ_NEXT((elm), field); \
} while (0)
#define STAILQ_LAST(head, type, field) \
(STAILQ_EMPTY((head)) ? \
NULL : \
((struct type *) \
((char *)((head)->stqh_last) - offsetof(struct type, field))))
#define STAILQ_NEXT(elm, field) ((elm)->field.stqe_next)
#define STAILQ_REMOVE(head, elm, type, field) do { \
if (STAILQ_FIRST((head)) == (elm)) { \
STAILQ_REMOVE_HEAD((head), field); \
} \
else { \
struct type *curelm = STAILQ_FIRST((head)); \
while (STAILQ_NEXT(curelm, field) != (elm)) \
curelm = STAILQ_NEXT(curelm, field); \
if ((STAILQ_NEXT(curelm, field) = \
STAILQ_NEXT(STAILQ_NEXT(curelm, field), field)) == NULL)\
(head)->stqh_last = &STAILQ_NEXT((curelm), field);\
} \
} while (0)
#define STAILQ_REMOVE_HEAD(head, field) do { \
if ((STAILQ_FIRST((head)) = \
STAILQ_NEXT(STAILQ_FIRST((head)), field)) == NULL) \
(head)->stqh_last = &STAILQ_FIRST((head)); \
} while (0)
#define STAILQ_REMOVE_HEAD_UNTIL(head, elm, field) do { \
if ((STAILQ_FIRST((head)) = STAILQ_NEXT((elm), field)) == NULL) \
(head)->stqh_last = &STAILQ_FIRST((head)); \
} while (0)
/*
* List declarations.
*/
#define LIST_HEAD(name, type) \
struct name { \
struct type *lh_first; /* first element */ \
}
#define LIST_HEAD_INITIALIZER(head) \
{ NULL }
#define LIST_ENTRY(type) \
struct { \
struct type *le_next; /* next element */ \
struct type **le_prev; /* address of previous next element */ \
}
/*
* List functions.
*/
#define LIST_EMPTY(head) ((head)->lh_first == NULL)
#define LIST_FIRST(head) ((head)->lh_first)
#define LIST_FOREACH(var, head, field) \
for ((var) = LIST_FIRST((head)); \
(var); \
(var) = LIST_NEXT((var), field))
#define LIST_FOREACH_SAFE(var, head, field, tvar) \
for ((var) = LIST_FIRST((head)); \
(var) && ((tvar) = LIST_NEXT((var), field), 1); \
(var) = (tvar))
#define LIST_INIT(head) do { \
LIST_FIRST((head)) = NULL; \
} while (0)
#define LIST_INSERT_AFTER(listelm, elm, field) do { \
if ((LIST_NEXT((elm), field) = LIST_NEXT((listelm), field)) != NULL)\
LIST_NEXT((listelm), field)->field.le_prev = \
&LIST_NEXT((elm), field); \
LIST_NEXT((listelm), field) = (elm); \
(elm)->field.le_prev = &LIST_NEXT((listelm), field); \
} while (0)
#define LIST_INSERT_BEFORE(listelm, elm, field) do { \
(elm)->field.le_prev = (listelm)->field.le_prev; \
LIST_NEXT((elm), field) = (listelm); \
*(listelm)->field.le_prev = (elm); \
(listelm)->field.le_prev = &LIST_NEXT((elm), field); \
} while (0)
#define LIST_INSERT_HEAD(head, elm, field) do { \
if ((LIST_NEXT((elm), field) = LIST_FIRST((head))) != NULL) \
LIST_FIRST((head))->field.le_prev = &LIST_NEXT((elm), field);\
LIST_FIRST((head)) = (elm); \
(elm)->field.le_prev = &LIST_FIRST((head)); \
} while (0)
#define LIST_NEXT(elm, field) ((elm)->field.le_next)
#define LIST_REMOVE(elm, field) do { \
if (LIST_NEXT((elm), field) != NULL) \
LIST_NEXT((elm), field)->field.le_prev = \
(elm)->field.le_prev; \
*(elm)->field.le_prev = LIST_NEXT((elm), field); \
} while (0)
/*
* Tail queue declarations.
*/
#define TAILQ_HEAD(name, type) \
struct name { \
struct type *tqh_first; /* first element */ \
struct type **tqh_last; /* addr of last next element */ \
}
#define TAILQ_HEAD_INITIALIZER(head) \
{ NULL, &(head).tqh_first }
#define TAILQ_ENTRY(type) \
struct { \
struct type *tqe_next; /* next element */ \
struct type **tqe_prev; /* address of previous next element */ \
}
/*
* Tail queue functions.
*/
#define TAILQ_CONCAT(head1, head2, field) do { \
if (!TAILQ_EMPTY(head2)) { \
*(head1)->tqh_last = (head2)->tqh_first; \
(head2)->tqh_first->field.tqe_prev = (head1)->tqh_last; \
(head1)->tqh_last = (head2)->tqh_last; \
TAILQ_INIT((head2)); \
} \
} while (0)
#define TAILQ_EMPTY(head) ((head)->tqh_first == NULL)
#define TAILQ_FIRST(head) ((head)->tqh_first)
#define TAILQ_FOREACH(var, head, field) \
for ((var) = TAILQ_FIRST((head)); \
(var); \
(var) = TAILQ_NEXT((var), field))
#define TAILQ_FOREACH_SAFE(var, head, field, tvar) \
for ((var) = TAILQ_FIRST((head)); \
(var) && ((tvar) = TAILQ_NEXT((var), field), 1); \
(var) = (tvar))
#define TAILQ_FOREACH_REVERSE(var, head, headname, field) \
for ((var) = TAILQ_LAST((head), headname); \
(var); \
(var) = TAILQ_PREV((var), headname, field))
#define TAILQ_FOREACH_REVERSE_SAFE(var, head, headname, field, tvar) \
for ((var) = TAILQ_LAST((head), headname); \
(var) && ((tvar) = TAILQ_PREV((var), headname, field), 1); \
(var) = (tvar))
#define TAILQ_INIT(head) do { \
TAILQ_FIRST((head)) = NULL; \
(head)->tqh_last = &TAILQ_FIRST((head)); \
} while (0)
#define TAILQ_INSERT_AFTER(head, listelm, elm, field) do { \
if ((TAILQ_NEXT((elm), field) = TAILQ_NEXT((listelm), field)) != NULL)\
TAILQ_NEXT((elm), field)->field.tqe_prev = \
&TAILQ_NEXT((elm), field); \
else { \
(head)->tqh_last = &TAILQ_NEXT((elm), field); \
} \
TAILQ_NEXT((listelm), field) = (elm); \
(elm)->field.tqe_prev = &TAILQ_NEXT((listelm), field); \
} while (0)
#define TAILQ_INSERT_BEFORE(listelm, elm, field) do { \
(elm)->field.tqe_prev = (listelm)->field.tqe_prev; \
TAILQ_NEXT((elm), field) = (listelm); \
*(listelm)->field.tqe_prev = (elm); \
(listelm)->field.tqe_prev = &TAILQ_NEXT((elm), field); \
} while (0)
#define TAILQ_INSERT_HEAD(head, elm, field) do { \
if ((TAILQ_NEXT((elm), field) = TAILQ_FIRST((head))) != NULL) \
TAILQ_FIRST((head))->field.tqe_prev = \
&TAILQ_NEXT((elm), field); \
else \
(head)->tqh_last = &TAILQ_NEXT((elm), field); \
TAILQ_FIRST((head)) = (elm); \
(elm)->field.tqe_prev = &TAILQ_FIRST((head)); \
} while (0)
#define TAILQ_INSERT_TAIL(head, elm, field) do { \
TAILQ_NEXT((elm), field) = NULL; \
(elm)->field.tqe_prev = (head)->tqh_last; \
*(head)->tqh_last = (elm); \
(head)->tqh_last = &TAILQ_NEXT((elm), field); \
} while (0)
#define TAILQ_LAST(head, headname) \
(*(((struct headname *)((head)->tqh_last))->tqh_last))
#define TAILQ_NEXT(elm, field) ((elm)->field.tqe_next)
#define TAILQ_PREV(elm, headname, field) \
(*(((struct headname *)((elm)->field.tqe_prev))->tqh_last))
#define TAILQ_REMOVE(head, elm, field) do { \
if ((TAILQ_NEXT((elm), field)) != NULL) \
TAILQ_NEXT((elm), field)->field.tqe_prev = \
(elm)->field.tqe_prev; \
else { \
(head)->tqh_last = (elm)->field.tqe_prev; \
} \
*(elm)->field.tqe_prev = TAILQ_NEXT((elm), field); \
} while (0)
#endif /* !SYS_QUEUE_H */

View File

@ -0,0 +1,393 @@
/**
* \file libyasm/coretype.h
* \brief YASM core types and utility functions.
*
* \license
* Copyright (C) 2001-2007 Peter Johnson
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* - Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* - Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND OTHER CONTRIBUTORS ``AS IS''
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR OTHER CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
* \endlicense
*/
#ifndef YASM_CORETYPE_H
#define YASM_CORETYPE_H
#ifndef YASM_LIB_DECL
#define YASM_LIB_DECL
#endif
/** Architecture instance (mostly opaque type). \see arch.h for details. */
typedef struct yasm_arch yasm_arch;
/** Preprocessor interface. \see preproc.h for details. */
typedef struct yasm_preproc yasm_preproc;
/** Parser instance (mostly opaque type). \see parser.h for details. */
typedef struct yasm_parser yasm_parser;
/** Object format interface. \see objfmt.h for details. */
typedef struct yasm_objfmt yasm_objfmt;
/** Debug format interface. \see dbgfmt.h for details. */
typedef struct yasm_dbgfmt yasm_dbgfmt;
/** List format interface. \see listfmt.h for details. */
typedef struct yasm_listfmt yasm_listfmt;
/** Object format module interface. \see objfmt.h for details. */
typedef struct yasm_objfmt_module yasm_objfmt_module;
/** Debug format module interface. \see dbgfmt.h for details. */
typedef struct yasm_dbgfmt_module yasm_dbgfmt_module;
/** Standard macro structure for modules that allows association of a set of
* standard macros with a parser/preprocessor combination.
* A NULL-terminated array of these structures is used in a number of module
* interfaces.
*/
typedef struct yasm_stdmac {
const char *parser; /**< Parser keyword */
const char *preproc; /**< Preprocessor keyword */
/** NULL-terminated array of standard macros. May be NULL if no standard
* macros should be added for this preprocessor.
*/
const char **macros;
} yasm_stdmac;
/** YASM associated data callback structure. Many data structures can have
* arbitrary data associated with them.
*/
typedef struct yasm_assoc_data_callback {
/** Free memory allocated for associated data.
* \param data associated data
*/
void (*destroy) (/*@only@*/ void *data);
/** Print a description of allocated data. For debugging purposes.
* \param data associated data
* \param f output file
* \param indent_level indentation level
*/
void (*print) (void *data, FILE *f, int indent_level);
} yasm_assoc_data_callback;
/** Set of collected error/warnings (opaque type).
* \see errwarn.h for details.
*/
typedef struct yasm_errwarns yasm_errwarns;
/** Bytecode. \see bytecode.h for details and related functions. */
typedef struct yasm_bytecode yasm_bytecode;
/** Object. \see section.h for details and related functions. */
typedef struct yasm_object yasm_object;
/** Section (opaque type). \see section.h for related functions. */
typedef struct yasm_section yasm_section;
/** Symbol table (opaque type). \see symrec.h for related functions. */
typedef struct yasm_symtab yasm_symtab;
/** Symbol record (opaque type). \see symrec.h for related functions. */
typedef struct yasm_symrec yasm_symrec;
/** Expression. \see expr.h for details and related functions. */
typedef struct yasm_expr yasm_expr;
/** Integer value (opaque type). \see intnum.h for related functions. */
typedef struct yasm_intnum yasm_intnum;
/** Floating point value (opaque type).
* \see floatnum.h for related functions.
*/
typedef struct yasm_floatnum yasm_floatnum;
/** A value. May be absolute or relative. Outside the parser, yasm_expr
* should only be used for absolute exprs. Anything that could contain
* a relocatable value should use this structure instead.
* \see value.h for related functions.
*/
typedef struct yasm_value {
/** The absolute portion of the value. May contain *differences* between
* symrecs but not standalone symrecs. May be NULL if there is no
* absolute portion (e.g. the absolute portion is 0).
*/
/*@null@*/ /*@only@*/ yasm_expr *abs;
/** The relative portion of the value. This is the portion that may
* need to generate a relocation. May be NULL if no relative portion.
*/
/*@null@*/ /*@dependent@*/ yasm_symrec *rel;
/** What the relative portion is in reference to. NULL if the default. */
/*@null@*/ /*@dependent@*/ yasm_symrec *wrt;
/** If the segment of the relative portion should be used, not the
* relative portion itself. Boolean.
*/
unsigned int seg_of : 1;
/** If the relative portion of the value should be shifted right
* (supported only by a few object formats). If just the absolute portion
* should be shifted, that must be in the abs expr, not here!
*/
unsigned int rshift : 7;
/** Indicates the relative portion of the value should be relocated
* relative to the current assembly position rather than relative to the
* section start. "Current assembly position" here refers to the starting
* address of the bytecode containing this value. Boolean.
*/
unsigned int curpos_rel : 1;
/** Indicates that curpos_rel was set due to IP-relative relocation;
* in some objfmt/arch combinations (e.g. win64/x86-amd64) this info
* is needed to generate special relocations.
*/
unsigned int ip_rel : 1;
/** Indicates the value is a jump target address (rather than a simple
* data address). In some objfmt/arch combinations (e.g. macho/amd64)
* this info is needed to generate special relocations.
*/
unsigned int jump_target : 1;
/** Indicates the relative portion of the value should be relocated
* relative to its own section start rather than relative to the
* section start of the bytecode containing this value. E.g. the value
* resulting from the relative portion should be the offset from its
* section start. Boolean.
*/
unsigned int section_rel : 1;
/** Indicates overflow warnings have been disabled for this value. */
unsigned int no_warn : 1;
/** Sign of the value. Nonzero if the final value should be treated as
* signed, 0 if it should be treated as signed.
*/
unsigned int sign : 1;
/** Size of the value, in bits. */
unsigned int size : 8;
} yasm_value;
/** Maximum value of #yasm_value.rshift */
#define YASM_VALUE_RSHIFT_MAX 127
/** Line number mapping repository (opaque type). \see linemap.h for related
* functions.
*/
typedef struct yasm_linemap yasm_linemap;
/** Value/parameter pair (opaque type).
* \see valparam.h for related functions.
*/
typedef struct yasm_valparam yasm_valparam;
/** List of value/parameters (opaque type).
* \see valparam.h for related functions.
*/
typedef struct yasm_valparamhead yasm_valparamhead;
/** Directive list entry.
* \see valparam.h for details and related functions.
*/
typedef struct yasm_directive yasm_directive;
/** An effective address.
* \see insn.h for related functions.
*/
typedef struct yasm_effaddr yasm_effaddr;
/** An instruction.
* \see insn.h for related functions.
*/
typedef struct yasm_insn yasm_insn;
/** Expression operators usable in #yasm_expr expressions. */
typedef enum yasm_expr_op {
YASM_EXPR_IDENT, /**< No operation, just a value. */
YASM_EXPR_ADD, /**< Arithmetic addition (+). */
YASM_EXPR_SUB, /**< Arithmetic subtraction (-). */
YASM_EXPR_MUL, /**< Arithmetic multiplication (*). */
YASM_EXPR_DIV, /**< Arithmetic unsigned division. */
YASM_EXPR_SIGNDIV, /**< Arithmetic signed division. */
YASM_EXPR_MOD, /**< Arithmetic unsigned modulus. */
YASM_EXPR_SIGNMOD, /**< Arithmetic signed modulus. */
YASM_EXPR_NEG, /**< Arithmetic negation (-). */
YASM_EXPR_NOT, /**< Bitwise negation. */
YASM_EXPR_OR, /**< Bitwise OR. */
YASM_EXPR_AND, /**< Bitwise AND. */
YASM_EXPR_XOR, /**< Bitwise XOR. */
YASM_EXPR_XNOR, /**< Bitwise XNOR. */
YASM_EXPR_NOR, /**< Bitwise NOR. */
YASM_EXPR_SHL, /**< Shift left (logical). */
YASM_EXPR_SHR, /**< Shift right (logical). */
YASM_EXPR_LOR, /**< Logical OR. */
YASM_EXPR_LAND, /**< Logical AND. */
YASM_EXPR_LNOT, /**< Logical negation. */
YASM_EXPR_LXOR, /**< Logical XOR. */
YASM_EXPR_LXNOR, /**< Logical XNOR. */
YASM_EXPR_LNOR, /**< Logical NOR. */
YASM_EXPR_LT, /**< Less than comparison. */
YASM_EXPR_GT, /**< Greater than comparison. */
YASM_EXPR_EQ, /**< Equality comparison. */
YASM_EXPR_LE, /**< Less than or equal to comparison. */
YASM_EXPR_GE, /**< Greater than or equal to comparison. */
YASM_EXPR_NE, /**< Not equal comparison. */
YASM_EXPR_NONNUM, /**< Start of non-numeric operations (not an op). */
YASM_EXPR_SEG, /**< SEG operator (gets segment portion of address). */
YASM_EXPR_WRT, /**< WRT operator (gets offset of address relative to
* some other segment). */
YASM_EXPR_SEGOFF /**< The ':' in segment:offset. */
} yasm_expr_op;
/** Convert yasm_value to its byte representation. Usually implemented by
* object formats to keep track of relocations and verify legal expressions.
* Must put the value into the least significant bits of the destination,
* unless shifted into more significant bits by the shift parameter. The
* destination bits must be cleared before being set.
* \param value value
* \param buf buffer for byte representation
* \param destsize destination size (in bytes)
* \param offset offset (in bytes) of the expr contents from the start
* of the bytecode (needed for relative)
* \param bc current bytecode (usually passed into higher-level
* calling function)
* \param warn enables standard warnings: zero for none;
* nonzero for overflow/underflow floating point warnings
* \param d objfmt-specific data (passed into higher-level calling
* function)
* \return Nonzero if an error occurred, 0 otherwise.
*/
typedef int (*yasm_output_value_func)
(yasm_value *value, /*@out@*/ unsigned char *buf, unsigned int destsize,
unsigned long offset, yasm_bytecode *bc, int warn, /*@null@*/ void *d);
/** Convert a symbol reference to its byte representation. Usually implemented
* by object formats and debug formats to keep track of relocations generated
* by themselves.
* \param sym symbol
* \param bc current bytecode (usually passed into higher-level
* calling function)
* \param buf buffer for byte representation
* \param destsize destination size (in bytes)
* \param valsize size (in bits)
* \param warn enables standard warnings: zero for none;
* nonzero for overflow/underflow floating point warnings;
* negative for signed integer warnings,
* positive for unsigned integer warnings
* \param d objfmt-specific data (passed into higher-level calling
* function)
* \return Nonzero if an error occurred, 0 otherwise.
*/
typedef int (*yasm_output_reloc_func)
(yasm_symrec *sym, yasm_bytecode *bc, unsigned char *buf,
unsigned int destsize, unsigned int valsize, int warn, void *d);
/** Sort an array using merge sort algorithm.
* \internal
* \param base base of array
* \param nmemb number of elements in array
* \param size size of each array element
* \param compar element comparison function
*/
YASM_LIB_DECL
int yasm__mergesort(void *base, size_t nmemb, size_t size,
int (*compar)(const void *, const void *));
/** Separate string by delimiters.
* \internal
* \param stringp string
* \param delim set of 1 or more delimiters
* \return First/next substring.
*/
YASM_LIB_DECL
/*@null@*/ char *yasm__strsep(char **stringp, const char *delim);
/** Compare two strings, ignoring case differences.
* \internal
* \param s1 string 1
* \param s2 string 2
* \return 0 if strings are equal, -1 if s1<s2, 1 if s1>s2.
*/
YASM_LIB_DECL
int yasm__strcasecmp(const char *s1, const char *s2);
/** Compare portion of two strings, ignoring case differences.
* \internal
* \param s1 string 1
* \param s2 string 2
* \param n maximum number of characters to compare
* \return 0 if strings are equal, -1 if s1<s2, 1 if s1>s2.
*/
YASM_LIB_DECL
int yasm__strncasecmp(const char *s1, const char *s2, size_t n);
/** strdup() implementation using yasm_xmalloc().
* \internal
* \param str string
* \return Newly allocated duplicate string.
*/
YASM_LIB_DECL
/*@only@*/ char *yasm__xstrdup(const char *str);
/** strndup() implementation using yasm_xmalloc().
* \internal
* \param str string
* \param max maximum number of characters to copy
* \return Newly allocated duplicate string.
*/
YASM_LIB_DECL
/*@only@*/ char *yasm__xstrndup(const char *str, size_t max);
/** Error-checking memory allocation. A default implementation is provided
* that calls yasm_fatal() on allocation errors.
* A replacement should \em never return NULL.
* \param size number of bytes to allocate
* \return Allocated memory block.
*/
YASM_LIB_DECL
extern /*@only@*/ /*@out@*/ void * (*yasm_xmalloc) (size_t size);
/** Error-checking memory allocation (with clear-to-0). A default
* implementation is provided that calls yasm_fatal() on allocation errors.
* A replacement should \em never return NULL.
* \param size number of elements to allocate
* \param elsize size (in bytes) of each element
* \return Allocated and cleared memory block.
*/
YASM_LIB_DECL
extern /*@only@*/ void * (*yasm_xcalloc) (size_t nelem, size_t elsize);
/** Error-checking memory reallocation. A default implementation is provided
* that calls yasm_fatal() on allocation errors. A replacement should
* \em never return NULL.
* \param oldmem memory block to resize
* \param elsize new size, in bytes
* \return Re-allocated memory block.
*/
YASM_LIB_DECL
extern /*@only@*/ void * (*yasm_xrealloc)
(/*@only@*/ /*@out@*/ /*@returned@*/ /*@null@*/ void *oldmem, size_t size)
/*@modifies oldmem@*/;
/** Error-checking memory deallocation. A default implementation is provided
* that calls yasm_fatal() on allocation errors.
* \param p memory block to free
*/
YASM_LIB_DECL
extern void (*yasm_xfree) (/*@only@*/ /*@out@*/ /*@null@*/ void *p)
/*@modifies p@*/;
#endif

View File

@ -0,0 +1,122 @@
/**
* \file libyasm/dbgfmt.h
* \brief YASM debug format interface.
*
* \license
* Copyright (C) 2002-2007 Peter Johnson
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* - Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* - Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND OTHER CONTRIBUTORS ``AS IS''
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR OTHER CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
* \endlicense
*/
#ifndef YASM_DBGFMT_H
#define YASM_DBGFMT_H
#ifndef YASM_DOXYGEN
/** Base #yasm_dbgfmt structure. Must be present as the first element in any
* #yasm_dbgfmt implementation.
*/
typedef struct yasm_dbgfmt_base {
/** #yasm_dbgfmt_module implementation for this debug format. */
const struct yasm_dbgfmt_module *module;
} yasm_dbgfmt_base;
#endif
/** Debug format module interface. */
struct yasm_dbgfmt_module {
/** One-line description of the debug format. */
const char *name;
/** Keyword used to select debug format. */
const char *keyword;
/** NULL-terminated list of directives. NULL if none. */
/*@null@*/ const yasm_directive *directives;
/** Create debug format.
* Module-level implementation of yasm_dbgfmt_create().
* The filenames are provided solely for informational purposes.
* \param object object
* \return NULL if object format does not provide needed support.
*/
/*@null@*/ /*@only@*/ yasm_dbgfmt * (*create) (yasm_object *object);
/** Module-level implementation of yasm_dbgfmt_destroy().
* Call yasm_dbgfmt_destroy() instead of calling this function.
*/
void (*destroy) (/*@only@*/ yasm_dbgfmt *dbgfmt);
/** Module-level implementation of yasm_dbgfmt_generate().
* Call yasm_dbgfmt_generate() instead of calling this function.
*/
void (*generate) (yasm_object *object, yasm_linemap *linemap,
yasm_errwarns *errwarns);
};
/** Get the keyword used to select a debug format.
* \param dbgfmt debug format
* \return keyword
*/
const char *yasm_dbgfmt_keyword(const yasm_dbgfmt *dbgfmt);
/** Initialize debug output for use. Must call before any other debug
* format functions. The filenames are provided solely for informational
* purposes.
* \param module debug format module
* \param object object to generate debugging information for
* \return NULL if object format does not provide needed support.
*/
/*@null@*/ /*@only@*/ yasm_dbgfmt *yasm_dbgfmt_create
(const yasm_dbgfmt_module *module, yasm_object *object);
/** Cleans up any allocated debug format memory.
* \param dbgfmt debug format
*/
void yasm_dbgfmt_destroy(/*@only@*/ yasm_dbgfmt *dbgfmt);
/** Generate debugging information bytecodes.
* \param object object
* \param linemap virtual/physical line mapping
* \param errwarns error/warning set
* \note Errors and warnings are stored into errwarns.
*/
void yasm_dbgfmt_generate(yasm_object *object, yasm_linemap *linemap,
yasm_errwarns *errwarns);
#ifndef YASM_DOXYGEN
/* Inline macro implementations for dbgfmt functions */
#define yasm_dbgfmt_keyword(dbgfmt) \
(((yasm_dbgfmt_base *)dbgfmt)->module->keyword)
#define yasm_dbgfmt_create(module, object) \
module->create(object)
#define yasm_dbgfmt_destroy(dbgfmt) \
((yasm_dbgfmt_base *)dbgfmt)->module->destroy(dbgfmt)
#define yasm_dbgfmt_generate(object, linemap, ews) \
((yasm_dbgfmt_base *)((object)->dbgfmt))->module->generate \
(object, linemap, ews)
#endif
#endif

View File

@ -0,0 +1,348 @@
/**
* \file libyasm/errwarn.h
* \brief YASM error and warning reporting interface.
*
* \license
* Copyright (C) 2001-2007 Peter Johnson
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* - Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* - Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND OTHER CONTRIBUTORS ``AS IS''
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR OTHER CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
* \endlicense
*/
#ifndef YASM_ERRWARN_H
#define YASM_ERRWARN_H
#ifndef YASM_LIB_DECL
#define YASM_LIB_DECL
#endif
/** Warning classes (that may be enabled/disabled). */
typedef enum yasm_warn_class {
YASM_WARN_NONE = 0, /**< No warning */
YASM_WARN_GENERAL, /**< Non-specific warnings */
YASM_WARN_UNREC_CHAR, /**< Unrecognized characters (while tokenizing) */
YASM_WARN_PREPROC, /**< Preprocessor warnings */
YASM_WARN_ORPHAN_LABEL, /**< Label alone on a line without a colon */
YASM_WARN_UNINIT_CONTENTS, /**< Uninitialized space in code/data section */
YASM_WARN_SIZE_OVERRIDE,/**< Double size override */
YASM_WARN_IMPLICIT_SIZE_OVERRIDE /**< Implicit size override */
} yasm_warn_class;
/** Error classes. Bitmask-based to support limited subclassing. */
typedef enum yasm_error_class {
YASM_ERROR_NONE = 0x0000, /**< No error */
YASM_ERROR_GENERAL = 0xFFFF, /**< Non-specific */
YASM_ERROR_ARITHMETIC = 0x0001, /**< Arithmetic error (general) */
YASM_ERROR_OVERFLOW = 0x8001, /**< Arithmetic overflow */
YASM_ERROR_FLOATING_POINT = 0x4001, /**< Floating point error */
YASM_ERROR_ZERO_DIVISION = 0x2001, /**< Divide-by-zero */
YASM_ERROR_ASSERTION = 0x0002, /**< Assertion error */
YASM_ERROR_VALUE = 0x0004, /**< Value inappropriate
* (e.g. not in range) */
YASM_ERROR_NOT_ABSOLUTE = 0x8004, /**< Absolute expression required */
YASM_ERROR_TOO_COMPLEX = 0x4004, /**< Expression too complex */
YASM_ERROR_NOT_CONSTANT = 0x2004, /**< Constant expression required */
YASM_ERROR_IO = 0x0008, /**< I/O error */
YASM_ERROR_NOT_IMPLEMENTED = 0x0010, /**< Not implemented error */
YASM_ERROR_TYPE = 0x0020, /**< Type error */
YASM_ERROR_SYNTAX = 0x0040, /**< Syntax error */
YASM_ERROR_PARSE = 0x8040 /**< Parser error */
} yasm_error_class;
/** Initialize any internal data structures. */
YASM_LIB_DECL
void yasm_errwarn_initialize(void);
/** Clean up any memory allocated by yasm_errwarn_initialize() or other
* functions.
*/
YASM_LIB_DECL
void yasm_errwarn_cleanup(void);
/** Reporting point of internal errors. These are usually due to sanity
* check failures in the code.
* \warning This function must NOT return to calling code; exit or longjmp
* instead.
* \param file source file (ala __FILE__)
* \param line source line (ala __LINE__)
* \param message internal error message
*/
YASM_LIB_DECL
extern /*@exits@*/ void (*yasm_internal_error_)
(const char *file, unsigned int line, const char *message);
/** Easily-callable version of yasm_internal_error_(). Automatically uses
* __FILE__ and __LINE__ as the file and line.
* \param message internal error message
*/
#define yasm_internal_error(message) \
yasm_internal_error_(__FILE__, __LINE__, message)
/** Reporting point of fatal errors.
* \warning This function must NOT return to calling code; exit or longjmp
* instead.
* \param message fatal error message
* \param va va_list argument list for message
*/
YASM_LIB_DECL
extern /*@exits@*/ void (*yasm_fatal) (const char *message, va_list va);
/** Reporting point of fatal errors, with variable arguments (internal only).
* \warning This function calls #yasm_fatal, and thus does not return to the
* calling code.
* \param message fatal error message
* \param ... argument list for message
*/
YASM_LIB_DECL
/*@exits@*/ void yasm__fatal(const char *message, ...);
/** Unconditionally clear the error indicator, freeing any associated data.
* Has no effect if the error indicator is not set.
*/
YASM_LIB_DECL
void yasm_error_clear(void);
/** Get the error indicator. YASM_ERROR_NONE is returned if no error has
* been set. Note that as YASM_ERROR_NONE is 0, the return value can also
* be treated as a boolean value.
* \return Current error indicator.
*/
yasm_error_class yasm_error_occurred(void);
/** Check the error indicator against an error class. To check if any error
* has been set, check against the YASM_ERROR_GENERAL class. This function
* properly checks error subclasses.
* \param eclass base error class to check against
* \return Nonzero if error indicator is set and a subclass of eclass, 0
* otherwise.
*/
YASM_LIB_DECL
int yasm_error_matches(yasm_error_class eclass);
#ifndef YASM_DOXYGEN
YASM_LIB_DECL
extern yasm_error_class yasm_eclass;
#define yasm_error_occurred() yasm_eclass
#endif
/** Set the error indicator (va_list version). Has no effect if the error
* indicator is already set.
* \param eclass error class
* \param format printf format string
* \param va argument list for format
*/
YASM_LIB_DECL
void yasm_error_set_va(yasm_error_class eclass, const char *format, va_list va);
/** Set the error indicator. Has no effect if the error indicator is already
* set.
* \param eclass error class
* \param format printf format string
* \param ... argument list for format
*/
YASM_LIB_DECL
void yasm_error_set(yasm_error_class eclass, const char *format, ...)
/*@printflike@*/;
/** Set a cross-reference for a new error (va_list version). Has no effect
* if the error indicator is already set (e.g. with yasm_error_set()). This
* function must be called prior to its corresponding yasm_error_set() call.
* \param xrefline virtual line to cross-reference to (should not be 0)
* \param format printf format string
* \param va argument list for format
*/
YASM_LIB_DECL
void yasm_error_set_xref_va(unsigned long xrefline, const char *format,
va_list va);
/** Set a cross-reference for a new error. Has no effect if the error
* indicator is already set (e.g. with yasm_error_set()). This function
* must be called prior to its corresponding yasm_error_set() call.
* \param xrefline virtual line to cross-reference to (should not be 0)
* \param format printf format string
* \param ... argument list for format
*/
YASM_LIB_DECL
void yasm_error_set_xref(unsigned long xrefline, const char *format, ...)
/*@printflike@*/;
/** Fetch the error indicator and all associated data. If the error
* indicator is set, the output pointers are set to the current error
* indicator values, and the error indicator is cleared.
* The code using this function is then responsible for yasm_xfree()'ing
* str and xrefstr (if non-NULL). If the error indicator is not set,
* all output values are set to 0 (including eclass, which is set to
* YASM_ERROR_NONE).
* \param eclass error class (output)
* \param str error message
* \param xrefline virtual line used for cross-referencing (0 if no xref)
* \param xrefstr cross-reference error message (NULL if no xref)
*/
YASM_LIB_DECL
void yasm_error_fetch(/*@out@*/ yasm_error_class *eclass,
/*@out@*/ /*@only@*/ /*@null@*/ char **str,
/*@out@*/ unsigned long *xrefline,
/*@out@*/ /*@only@*/ /*@null@*/ char **xrefstr);
/** Unconditionally clear all warning indicators, freeing any associated data.
* Has no effect if no warning indicators have been set.
*/
YASM_LIB_DECL
void yasm_warn_clear(void);
/** Get the first warning indicator. YASM_WARN_NONE is returned if no warning
* has been set. Note that as YASM_WARN_NONE is 0, the return value can also
* be treated as a boolean value.
* \return First warning indicator.
*/
YASM_LIB_DECL
yasm_warn_class yasm_warn_occurred(void);
/** Add a warning indicator (va_list version).
* \param wclass warning class
* \param format printf format string
* \param va argument list for format
*/
YASM_LIB_DECL
void yasm_warn_set_va(yasm_warn_class wclass, const char *format, va_list va);
/** Add a warning indicator.
* \param wclass warning class
* \param format printf format string
* \param ... argument list for format
*/
YASM_LIB_DECL
void yasm_warn_set(yasm_warn_class wclass, const char *format, ...)
/*@printflike@*/;
/** Fetch the first warning indicator and all associated data. If there
* is at least one warning indicator, the output pointers are set to the
* first warning indicator values, and first warning indicator is removed.
* The code using this function is then responsible for yasm_xfree()'ing
* str and xrefstr (if non-NULL). If there is no warning indicator set,
* all output values are set to 0 (including wclass, which is set to
* YASM_WARN_NONE).
* \param wclass warning class (output)
* \param str warning message
*/
YASM_LIB_DECL
void yasm_warn_fetch(/*@out@*/ yasm_warn_class *wclass,
/*@out@*/ /*@only@*/ char **str);
/** Enable a class of warnings.
* \param wclass warning class
*/
YASM_LIB_DECL
void yasm_warn_enable(yasm_warn_class wclass);
/** Disable a class of warnings.
* \param wclass warning class
*/
YASM_LIB_DECL
void yasm_warn_disable(yasm_warn_class wclass);
/** Disable all classes of warnings. */
YASM_LIB_DECL
void yasm_warn_disable_all(void);
/** Create an error/warning set for collection of multiple error/warnings.
* \return Newly allocated set.
*/
YASM_LIB_DECL
/*@only@*/ yasm_errwarns *yasm_errwarns_create(void);
/** Destroy an error/warning set.
* \param errwarns error/warning set
*/
YASM_LIB_DECL
void yasm_errwarns_destroy(/*@only@*/ yasm_errwarns *errwarns);
/** Propagate error indicator and warning indicator(s) to an error/warning set.
* Has no effect if the error indicator and warning indicator are not set.
* Does not print immediately; yasm_errwarn_output_all() outputs
* accumulated errors and warnings.
* Generally multiple errors on the same line will be reported, but errors
* of class YASM_ERROR_PARSE will get overwritten by any other class on the
* same line.
* \param errwarns error/warning set
* \param line virtual line
*/
YASM_LIB_DECL
void yasm_errwarn_propagate(yasm_errwarns *errwarns, unsigned long line);
/** Get total number of errors logged.
* \param errwarns error/warning set
* \param warning_as_error if nonzero, warnings are treated as errors.
* \return Number of errors.
*/
YASM_LIB_DECL
unsigned int yasm_errwarns_num_errors(yasm_errwarns *errwarns,
int warning_as_error);
/** Print out an error.
* \param fn filename of source file
* \param line line number
* \param msg error message
* \param xref_fn cross-referenced source filename
* \param xref_line cross-referenced line number
* \param xref_msg cross-referenced error message
*/
typedef void (*yasm_print_error_func)
(const char *fn, unsigned long line, const char *msg,
/*@null@*/ const char *xref_fn, unsigned long xref_line,
/*@null@*/ const char *xref_msg);
/** Print out a warning.
* \param fn filename of source file
* \param line line number
* \param msg warning message
*/
typedef void (*yasm_print_warning_func)
(const char *fn, unsigned long line, const char *msg);
/** Outputs error/warning set in sorted order (sorted by virtual line number).
* \param errwarns error/warning set
* \param lm line map (to convert virtual lines into filename/line pairs)
* \param warning_as_error if nonzero, treat warnings as errors.
* \param print_error function called to print out errors
* \param print_warning function called to print out warnings
*/
YASM_LIB_DECL
void yasm_errwarns_output_all
(yasm_errwarns *errwarns, yasm_linemap *lm, int warning_as_error,
yasm_print_error_func print_error, yasm_print_warning_func print_warning);
/** Convert a possibly unprintable character into a printable string.
* \internal
* \param ch possibly unprintable character
* \return Printable string representation (static buffer).
*/
YASM_LIB_DECL
char *yasm__conv_unprint(int ch);
/** Hook for library users to map to gettext() if GNU gettext is being used.
* \param msgid message catalog identifier
* \return Translated message.
*/
YASM_LIB_DECL
extern const char * (*yasm_gettext_hook) (const char *msgid);
#endif

View File

@ -0,0 +1,388 @@
/**
* \file libyasm/expr.h
* \brief YASM expression interface.
*
* \license
* Copyright (C) 2001-2007 Michael Urman, Peter Johnson
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* - Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* - Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND OTHER CONTRIBUTORS ``AS IS''
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR OTHER CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
* \endlicense
*/
#ifndef YASM_EXPR_H
#define YASM_EXPR_H
#ifndef YASM_LIB_DECL
#define YASM_LIB_DECL
#endif
/** Type of an expression item. Types are listed in canonical sorting order.
* See expr_order_terms().
* Note #YASM_EXPR_PRECBC must be used carefully (in a-b pairs), as only
* symrecs can become the relative term in a #yasm_value.
*/
typedef enum yasm_expr__type {
YASM_EXPR_NONE = 0, /**< Nothing */
YASM_EXPR_REG = 1<<0, /**< Register */
YASM_EXPR_INT = 1<<1, /**< Integer value */
YASM_EXPR_SUBST = 1<<2, /**< Substitution placeholder */
YASM_EXPR_FLOAT = 1<<3, /**< Floating point value */
YASM_EXPR_SYM = 1<<4, /**< Symbol */
YASM_EXPR_PRECBC = 1<<5,/**< Direct bytecode ref (rather than via sym) */
YASM_EXPR_EXPR = 1<<6 /**< Subexpression */
} yasm_expr__type;
/** Expression item. */
typedef struct yasm_expr__item {
yasm_expr__type type; /**< Type */
/** Expression item data. Correct value depends on type. */
union {
yasm_bytecode *precbc; /**< Direct bytecode ref (YASM_EXPR_PRECBC) */
yasm_symrec *sym; /**< Symbol (YASM_EXPR_SYM) */
yasm_expr *expn; /**< Subexpression (YASM_EXPR_EXPR) */
yasm_intnum *intn; /**< Integer value (YASM_EXPR_INT) */
yasm_floatnum *flt; /**< Floating point value (YASM_EXPR_FLOAT) */
uintptr_t reg; /**< Register (YASM_EXPR_REG) */
unsigned int subst; /**< Subst placeholder (YASM_EXPR_SUBST) */
} data;
} yasm_expr__item;
/** Expression. */
struct yasm_expr {
yasm_expr_op op; /**< Operation. */
unsigned long line; /**< Line number where expression was defined. */
int numterms; /**< Number of terms in the expression. */
/** Terms of the expression. Structure may be extended to include more
* terms, as some operations may allow more than two operand terms
* (ADD, MUL, OR, AND, XOR).
*/
yasm_expr__item terms[2];
};
/** Create a new expression e=a op b.
* \param op operation
* \param a expression item a
* \param b expression item b (optional depending on op)
* \param line virtual line (where expression defined)
* \return Newly allocated expression.
*/
YASM_LIB_DECL
/*@only@*/ yasm_expr *yasm_expr_create
(yasm_expr_op op, /*@only@*/ yasm_expr__item *a,
/*@only@*/ /*@null@*/ yasm_expr__item *b, unsigned long line);
/** Create a new preceding-bytecode expression item.
* \param precbc preceding bytecode
* \return Newly allocated expression item.
*/
YASM_LIB_DECL
/*@only@*/ yasm_expr__item *yasm_expr_precbc(/*@keep@*/ yasm_bytecode *precbc);
/** Create a new symbol expression item.
* \param sym symbol
* \return Newly allocated expression item.
*/
YASM_LIB_DECL
/*@only@*/ yasm_expr__item *yasm_expr_sym(/*@keep@*/ yasm_symrec *sym);
/** Create a new expression expression item.
* \param e expression
* \return Newly allocated expression item.
*/
YASM_LIB_DECL
/*@only@*/ yasm_expr__item *yasm_expr_expr(/*@keep@*/ yasm_expr *e);
/** Create a new intnum expression item.
* \param intn intnum
* \return Newly allocated expression item.
*/
YASM_LIB_DECL
/*@only@*/ yasm_expr__item *yasm_expr_int(/*@keep@*/ yasm_intnum *intn);
/** Create a new floatnum expression item.
* \param flt floatnum
* \return Newly allocated expression item.
*/
YASM_LIB_DECL
/*@only@*/ yasm_expr__item *yasm_expr_float(/*@keep@*/ yasm_floatnum *flt);
/** Create a new register expression item.
* \param reg register
* \return Newly allocated expression item.
*/
YASM_LIB_DECL
/*@only@*/ yasm_expr__item *yasm_expr_reg(uintptr_t reg);
/** Create a new expression tree e=l op r.
* \param l expression for left side of new expression
* \param o operation
* \param r expression for right side of new expression
* \param i line index
* \return Newly allocated expression.
*/
#define yasm_expr_create_tree(l,o,r,i) \
yasm_expr_create ((o), yasm_expr_expr(l), yasm_expr_expr(r), i)
/** Create a new expression branch e=op r.
* \param o operation
* \param r expression for right side of new expression
* \param i line index
* \return Newly allocated expression.
*/
#define yasm_expr_create_branch(o,r,i) \
yasm_expr_create ((o), yasm_expr_expr(r), (yasm_expr__item *)NULL, i)
/** Create a new expression identity e=r.
* \param r expression for identity within new expression
* \param i line index
* \return Newly allocated expression.
*/
#define yasm_expr_create_ident(r,i) \
yasm_expr_create (YASM_EXPR_IDENT, (r), (yasm_expr__item *)NULL, i)
/** Duplicate an expression.
* \param e expression
* \return Newly allocated expression identical to e.
*/
yasm_expr *yasm_expr_copy(const yasm_expr *e);
#ifndef YASM_DOXYGEN
#define yasm_expr_copy(e) yasm_expr__copy_except(e, -1)
#endif
/** Destroy (free allocated memory for) an expression.
* \param e expression
*/
YASM_LIB_DECL
void yasm_expr_destroy(/*@only@*/ /*@null@*/ yasm_expr *e);
/** Determine if an expression is a specified operation (at the top level).
* \param e expression
* \param op operator
* \return Nonzero if the expression was the specified operation at the top
* level, zero otherwise.
*/
YASM_LIB_DECL
int yasm_expr_is_op(const yasm_expr *e, yasm_expr_op op);
/** Extra transformation function for yasm_expr__level_tree().
* \param e expression being simplified
* \param d data provided as expr_xform_extra_data to
* yasm_expr__level_tree()
* \return Transformed e.
*/
typedef /*@only@*/ yasm_expr * (*yasm_expr_xform_func)
(/*@returned@*/ /*@only@*/ yasm_expr *e, /*@null@*/ void *d);
/** Level an entire expression tree.
* \internal
* \param e expression
* \param fold_const enable constant folding if nonzero
* \param simplify_ident simplify identities
* \param simplify_reg_mul simplify REG*1 identities
* \param calc_bc_dist nonzero if distances between bytecodes should be
* calculated, 0 if they should be left intact
* \param expr_xform_extra extra transformation function
* \param expr_xform_extra_data data to pass to expr_xform_extra
* \return Leveled expression.
*/
YASM_LIB_DECL
/*@only@*/ /*@null@*/ yasm_expr *yasm_expr__level_tree
(/*@returned@*/ /*@only@*/ /*@null@*/ yasm_expr *e, int fold_const,
int simplify_ident, int simplify_reg_mul, int calc_bc_dist,
/*@null@*/ yasm_expr_xform_func expr_xform_extra,
/*@null@*/ void *expr_xform_extra_data);
/** Simplify an expression as much as possible. Eliminates extraneous
* branches and simplifies integer-only subexpressions. Simplified version
* of yasm_expr__level_tree().
* \param e expression
* \param cbd if distance between bytecodes should be calculated
* \return Simplified expression.
*/
#define yasm_expr_simplify(e, cbd) \
yasm_expr__level_tree(e, 1, 1, 1, cbd, NULL, NULL)
/** Extract the segment portion of an expression containing SEG:OFF, leaving
* the offset.
* \param ep expression (pointer to)
* \return NULL if unable to extract a segment (expr does not contain a
* YASM_EXPR_SEGOFF operator), otherwise the segment expression.
* The input expression is modified such that on return, it's the
* offset expression.
*/
YASM_LIB_DECL
/*@only@*/ /*@null@*/ yasm_expr *yasm_expr_extract_deep_segoff(yasm_expr **ep);
/** Extract the segment portion of a SEG:OFF expression, leaving the offset.
* \param ep expression (pointer to)
* \return NULL if unable to extract a segment (YASM_EXPR_SEGOFF not the
* top-level operator), otherwise the segment expression. The input
* expression is modified such that on return, it's the offset
* expression.
*/
YASM_LIB_DECL
/*@only@*/ /*@null@*/ yasm_expr *yasm_expr_extract_segoff(yasm_expr **ep);
/** Extract the right portion (y) of a x WRT y expression, leaving the left
* portion (x).
* \param ep expression (pointer to)
* \return NULL if unable to extract (YASM_EXPR_WRT not the top-level
* operator), otherwise the right side of the WRT expression. The
* input expression is modified such that on return, it's the left side
* of the WRT expression.
*/
YASM_LIB_DECL
/*@only@*/ /*@null@*/ yasm_expr *yasm_expr_extract_wrt(yasm_expr **ep);
/** Get the integer value of an expression if it's just an integer.
* \param ep expression (pointer to)
* \param calc_bc_dist nonzero if distances between bytecodes should be
* calculated, 0 if NULL should be returned in this case
* \return NULL if the expression is too complex (contains anything other than
* integers, ie floats, non-valued labels, registers); otherwise the
* intnum value of the expression.
*/
YASM_LIB_DECL
/*@dependent@*/ /*@null@*/ yasm_intnum *yasm_expr_get_intnum
(yasm_expr **ep, int calc_bc_dist);
/** Get the symbol value of an expression if it's just a symbol.
* \param ep expression (pointer to)
* \param simplify if nonzero, simplify the expression first
* \return NULL if the expression is too complex; otherwise the symbol value of
* the expression.
*/
YASM_LIB_DECL
/*@dependent@*/ /*@null@*/ const yasm_symrec *yasm_expr_get_symrec
(yasm_expr **ep, int simplify);
/** Get the register value of an expression if it's just a register.
* \param ep expression (pointer to)
* \param simplify if nonzero, simplify the expression first
* \return NULL if the expression is too complex; otherwise the register value
* of the expression.
*/
YASM_LIB_DECL
/*@dependent@*/ /*@null@*/ const uintptr_t *yasm_expr_get_reg
(yasm_expr **ep, int simplify);
/** Print an expression. For debugging purposes.
* \param e expression
* \param f file
*/
YASM_LIB_DECL
void yasm_expr_print(/*@null@*/ const yasm_expr *e, FILE *f);
/** Return the size of an expression, if the user provided it
* \param e expression
*/
YASM_LIB_DECL
unsigned int yasm_expr_size(const yasm_expr *e);
/** Return the segment of an expression, if the user provided it
* \param e expression
*/
YASM_LIB_DECL
const char *yasm_expr_segment(const yasm_expr *e);
/** Traverse over expression tree in order (const version).
* Calls func for each leaf (non-operation).
* \param e expression
* \param d data passed to each call to func
* \param func callback function
* \return Stops early (and returns 1) if func returns 1.
* Otherwise returns 0.
*/
YASM_LIB_DECL
int yasm_expr__traverse_leaves_in_const
(const yasm_expr *e, /*@null@*/ void *d,
int (*func) (/*@null@*/ const yasm_expr__item *ei, /*@null@*/ void *d));
/** Traverse over expression tree in order.
* Calls func for each leaf (non-operation).
* \param e expression
* \param d data passed to each call to func
* \param func callback function
* \return Stops early (and returns 1) if func returns 1.
* Otherwise returns 0.
*/
YASM_LIB_DECL
int yasm_expr__traverse_leaves_in
(yasm_expr *e, /*@null@*/ void *d,
int (*func) (/*@null@*/ yasm_expr__item *ei, /*@null@*/ void *d));
/** Reorder terms of e into canonical order. Only reorders if reordering
* doesn't change meaning of expression. (eg, doesn't reorder SUB).
* Canonical order: REG, INT, FLOAT, SYM, EXPR.
* Multiple terms of a single type are kept in the same order as in
* the original expression.
* \param e expression
* \note Only performs reordering on *one* level (no recursion).
*/
YASM_LIB_DECL
void yasm_expr__order_terms(yasm_expr *e);
/** Copy entire expression EXCEPT for index "except" at *top level only*.
* \param e expression
* \param except term index not to copy; -1 to copy all terms
* \return Newly allocated copy of expression.
*/
YASM_LIB_DECL
yasm_expr *yasm_expr__copy_except(const yasm_expr *e, int except);
/** Test if expression contains an item. Searches recursively into
* subexpressions.
* \param e expression
* \param t type of item to look for
* \return Nonzero if expression contains an item of type t, zero if not.
*/
YASM_LIB_DECL
int yasm_expr__contains(const yasm_expr *e, yasm_expr__type t);
/** Transform symrec-symrec terms in expression into #YASM_EXPR_SUBST items.
* Calls the callback function for each symrec-symrec term.
* \param ep expression (pointer to)
* \param cbd callback data passed to callback function
* \param callback callback function: given subst index for bytecode
* pair, bytecode pair (bc2-bc1), and cbd (callback data)
* \return Number of transformations made.
*/
YASM_LIB_DECL
int yasm_expr__bc_dist_subst(yasm_expr **ep, void *cbd,
void (*callback) (unsigned int subst,
yasm_bytecode *precbc,
yasm_bytecode *precbc2,
void *cbd));
/** Substitute items into expr YASM_EXPR_SUBST items (by index). Items are
* copied, so caller is responsible for freeing array of items.
* \param e expression
* \param num_items number of items in items array
* \param items items array
* \return 1 on error (index out of range).
*/
YASM_LIB_DECL
int yasm_expr__subst(yasm_expr *e, unsigned int num_items,
const yasm_expr__item *items);
#endif

View File

@ -0,0 +1,525 @@
/**
* \file libyasm/file.h
* \brief YASM file helpers.
*
* \license
* Copyright (C) 2001-2007 Peter Johnson
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* - Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* - Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND OTHER CONTRIBUTORS ``AS IS''
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR OTHER CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
* \endlicense
*/
#ifndef YASM_FILE_H
#define YASM_FILE_H
#ifndef YASM_LIB_DECL
#define YASM_LIB_DECL
#endif
/** Re2c scanner state. */
typedef struct yasm_scanner {
unsigned char *bot; /**< Bottom of scan buffer */
unsigned char *tok; /**< Start of token */
unsigned char *ptr; /**< Scan marker */
unsigned char *cur; /**< Cursor (1 past end of token) */
unsigned char *lim; /**< Limit of good data */
unsigned char *top; /**< Top of scan buffer */
unsigned char *eof; /**< End of file */
} yasm_scanner;
/** Initialize scanner state.
* \param scanner Re2c scanner state
*/
YASM_LIB_DECL
void yasm_scanner_initialize(yasm_scanner *scanner);
/** Frees any memory used by scanner state; does not free state itself.
* \param scanner Re2c scanner state
*/
YASM_LIB_DECL
void yasm_scanner_delete(yasm_scanner *scanner);
/** Fill a scanner state structure with data coming from an input function.
* \param scanner Re2c scanner state
* \param cursor Re2c scan cursor
* \param input_func Input function to read data; takes buffer and maximum
* number of bytes, returns number of bytes read.
* \param input_func_data Data to pass as the first parameter to input_func
* \return 1 if this was the first time this function was called on this
* scanner state, 0 otherwise.
*/
YASM_LIB_DECL
int yasm_fill_helper
(yasm_scanner *scanner, unsigned char **cursor,
size_t (*input_func) (void *d, unsigned char *buf, size_t max),
void *input_func_data);
/** Unescape a string with C-style escapes. Handles b, f, n, r, t, and hex
* and octal escapes. String is updated in-place.
* Edge cases:
* - hex escapes: reads as many hex digits as possible, takes last 2 as value.
* - oct escapes: takes up to 3 digits 0-9 and scales appropriately, with
* warning.
* \param str C-style string (updated in place)
* \param len length of string (updated with new length)
*/
YASM_LIB_DECL
void yasm_unescape_cstring(unsigned char *str, size_t *len);
/** Split a UNIX pathname into head (directory) and tail (base filename)
* portions.
* \internal
* \param path pathname
* \param tail (returned) base filename
* \return Length of head (directory).
*/
YASM_LIB_DECL
size_t yasm__splitpath_unix(const char *path, /*@out@*/ const char **tail);
/** Split a Windows pathname into head (directory) and tail (base filename)
* portions.
* \internal
* \param path pathname
* \param tail (returned) base filename
* \return Length of head (directory).
*/
YASM_LIB_DECL
size_t yasm__splitpath_win(const char *path, /*@out@*/ const char **tail);
/** Split a pathname into head (directory) and tail (base filename) portions.
* Unless otherwise defined, defaults to yasm__splitpath_unix().
* \internal
* \param path pathname
* \param tail (returned) base filename
* \return Length of head (directory).
*/
#ifndef yasm__splitpath
# if defined (_WIN32) || defined (WIN32) || defined (__MSDOS__) || \
defined (__DJGPP__) || defined (__OS2__)
# define yasm__splitpath(path, tail) yasm__splitpath_win(path, tail)
# else
# define yasm__splitpath(path, tail) yasm__splitpath_unix(path, tail)
# endif
#endif
/** Get the current working directory.
* \internal
* \return Current working directory pathname (newly allocated).
*/
YASM_LIB_DECL
/*@only@*/ char *yasm__getcwd(void);
/** Convert a relative or absolute pathname into an absolute pathname.
* \internal
* \param path pathname
* \return Absolute version of path (newly allocated).
*/
YASM_LIB_DECL
/*@only@*/ char *yasm__abspath(const char *path);
/** Build a UNIX pathname that is equivalent to accessing the "to" pathname
* when you're in the directory containing "from". Result is relative if both
* from and to are relative.
* \internal
* \param from from pathname
* \param to to pathname
* \return Combined path (newly allocated).
*/
YASM_LIB_DECL
char *yasm__combpath_unix(const char *from, const char *to);
/** Build a Windows pathname that is equivalent to accessing the "to" pathname
* when you're in the directory containing "from". Result is relative if both
* from and to are relative.
* \internal
* \param from from pathname
* \param to to pathname
* \return Combined path (newly allocated).
*/
YASM_LIB_DECL
char *yasm__combpath_win(const char *from, const char *to);
/** Build a pathname that is equivalent to accessing the "to" pathname
* when you're in the directory containing "from". Result is relative if both
* from and to are relative.
* Unless otherwise defined, defaults to yasm__combpath_unix().
* \internal
* \param from from pathname
* \param to to pathname
* \return Combined path (newly allocated).
*/
#ifndef yasm__combpath
# if defined (_WIN32) || defined (WIN32) || defined (__MSDOS__) || \
defined (__DJGPP__) || defined (__OS2__)
# define yasm__combpath(from, to) yasm__combpath_win(from, to)
# else
# define yasm__combpath(from, to) yasm__combpath_unix(from, to)
# endif
#endif
/** Recursively create tree of directories needed for pathname.
* \internal
* \param path pathname
* \param win handle windows paths
* \return Length of directory portion of pathname.
*/
YASM_LIB_DECL
size_t yasm__createpath_common(const char *path, int win);
/** Recursively create tree of directories needed for pathname.
* Unless otherwise defined, defaults to yasm__createpath_unix().
* \internal
* \param path pathname
* \return Length of directory portion of pathname.
*/
#ifndef yasm__createpath
# if defined (_WIN32) || defined (WIN32) || defined (__MSDOS__) || \
defined (__DJGPP__) || defined (__OS2__)
# define yasm__createpath(path) yasm__createpath_common(path, 1)
# else
# define yasm__createpath(path) yasm__createpath_common(path, 0)
# endif
#endif
/** Try to find and open an include file, searching through include paths.
* First iname is looked for relative to the directory containing "from", then
* it's looked for relative to each of the include paths.
*
* All pathnames may be either absolute or relative; from, oname, and
* include paths, if relative, are relative from the current working directory.
*
* First match wins; the full pathname (newly allocated) to the opened file
* is saved into oname, and the fopen'ed FILE * is returned. If not found,
* NULL is returned.
*
* \param iname file to include
* \param from file doing the including
* \param mode fopen mode string
* \param oname full pathname of included file (may be relative). NULL
* may be passed if this is unwanted.
* \return fopen'ed include file, or NULL if not found.
*/
YASM_LIB_DECL
/*@null@*/ FILE *yasm_fopen_include
(const char *iname, const char *from, const char *mode,
/*@null@*/ /*@out@*/ /*@only@*/ char **oname);
/** Delete any stored include paths added by yasm_add_include_path().
*/
YASM_LIB_DECL
void yasm_delete_include_paths(void);
/** Iterate through include paths.
*/
YASM_LIB_DECL
const char * yasm_get_include_dir(void **iter);
/** Add an include path for use by yasm_fopen_include().
* If path is relative, it is treated by yasm_fopen_include() as relative to
* the current working directory.
*
* \param path path to add
*/
YASM_LIB_DECL
void yasm_add_include_path(const char *path);
/** Write an 8-bit value to a buffer, incrementing buffer pointer.
* \note Only works properly if ptr is an (unsigned char *).
* \param ptr buffer
* \param val 8-bit value
*/
#define YASM_WRITE_8(ptr, val) \
*((ptr)++) = (unsigned char)((val) & 0xFF)
/** Write a 16-bit value to a buffer in little endian, incrementing buffer
* pointer.
* \note Only works properly if ptr is an (unsigned char *).
* \param ptr buffer
* \param val 16-bit value
*/
#define YASM_WRITE_16_L(ptr, val) \
do { \
*((ptr)++) = (unsigned char)((val) & 0xFF); \
*((ptr)++) = (unsigned char)(((val) >> 8) & 0xFF); \
} while (0)
/** Write a 32-bit value to a buffer in little endian, incrementing buffer
* pointer.
* \note Only works properly if ptr is an (unsigned char *).
* \param ptr buffer
* \param val 32-bit value
*/
#define YASM_WRITE_32_L(ptr, val) \
do { \
*((ptr)++) = (unsigned char)((val) & 0xFF); \
*((ptr)++) = (unsigned char)(((val) >> 8) & 0xFF); \
*((ptr)++) = (unsigned char)(((val) >> 16) & 0xFF); \
*((ptr)++) = (unsigned char)(((val) >> 24) & 0xFF); \
} while (0)
/** Write a 16-bit value to a buffer in big endian, incrementing buffer
* pointer.
* \note Only works properly if ptr is an (unsigned char *).
* \param ptr buffer
* \param val 16-bit value
*/
#define YASM_WRITE_16_B(ptr, val) \
do { \
*((ptr)++) = (unsigned char)(((val) >> 8) & 0xFF); \
*((ptr)++) = (unsigned char)((val) & 0xFF); \
} while (0)
/** Write a 32-bit value to a buffer in big endian, incrementing buffer
* pointer.
* \note Only works properly if ptr is an (unsigned char *).
* \param ptr buffer
* \param val 32-bit value
*/
#define YASM_WRITE_32_B(ptr, val) \
do { \
*((ptr)++) = (unsigned char)(((val) >> 24) & 0xFF); \
*((ptr)++) = (unsigned char)(((val) >> 16) & 0xFF); \
*((ptr)++) = (unsigned char)(((val) >> 8) & 0xFF); \
*((ptr)++) = (unsigned char)((val) & 0xFF); \
} while (0)
/** Write an 8-bit value to a buffer. Does not increment buffer pointer.
* \note Only works properly if ptr is an (unsigned char *).
* \param ptr buffer
* \param val 8-bit value
*/
#define YASM_SAVE_8(ptr, val) \
*(ptr) = (unsigned char)((val) & 0xFF)
/** Write a 16-bit value to a buffer in little endian. Does not increment
* buffer pointer.
* \note Only works properly if ptr is an (unsigned char *).
* \param ptr buffer
* \param val 16-bit value
*/
#define YASM_SAVE_16_L(ptr, val) \
do { \
*(ptr) = (unsigned char)((val) & 0xFF); \
*((ptr)+1) = (unsigned char)(((val) >> 8) & 0xFF); \
} while (0)
/** Write a 32-bit value to a buffer in little endian. Does not increment
* buffer pointer.
* \note Only works properly if ptr is an (unsigned char *).
* \param ptr buffer
* \param val 32-bit value
*/
#define YASM_SAVE_32_L(ptr, val) \
do { \
*(ptr) = (unsigned char)((val) & 0xFF); \
*((ptr)+1) = (unsigned char)(((val) >> 8) & 0xFF); \
*((ptr)+2) = (unsigned char)(((val) >> 16) & 0xFF); \
*((ptr)+3) = (unsigned char)(((val) >> 24) & 0xFF); \
} while (0)
/** Write a 16-bit value to a buffer in big endian. Does not increment buffer
* pointer.
* \note Only works properly if ptr is an (unsigned char *).
* \param ptr buffer
* \param val 16-bit value
*/
#define YASM_SAVE_16_B(ptr, val) \
do { \
*(ptr) = (unsigned char)(((val) >> 8) & 0xFF); \
*((ptr)+1) = (unsigned char)((val) & 0xFF); \
} while (0)
/** Write a 32-bit value to a buffer in big endian. Does not increment buffer
* pointer.
* \note Only works properly if ptr is an (unsigned char *).
* \param ptr buffer
* \param val 32-bit value
*/
#define YASM_SAVE_32_B(ptr, val) \
do { \
*(ptr) = (unsigned char)(((val) >> 24) & 0xFF); \
*((ptr)+1) = (unsigned char)(((val) >> 16) & 0xFF); \
*((ptr)+2) = (unsigned char)(((val) >> 8) & 0xFF); \
*((ptr)+3) = (unsigned char)((val) & 0xFF); \
} while (0)
/** Direct-to-file version of YASM_SAVE_16_L().
* \note Using the macro multiple times with a single fwrite() call will
* probably be faster than calling this function many times.
* \param val 16-bit value
* \param f file
* \return 1 if the write was successful, 0 if not (just like fwrite()).
*/
YASM_LIB_DECL
size_t yasm_fwrite_16_l(unsigned short val, FILE *f);
/** Direct-to-file version of YASM_SAVE_32_L().
* \note Using the macro multiple times with a single fwrite() call will
* probably be faster than calling this function many times.
* \param val 32-bit value
* \param f file
* \return 1 if the write was successful, 0 if not (just like fwrite()).
*/
YASM_LIB_DECL
size_t yasm_fwrite_32_l(unsigned long val, FILE *f);
/** Direct-to-file version of YASM_SAVE_16_B().
* \note Using the macro multiple times with a single fwrite() call will
* probably be faster than calling this function many times.
* \param val 16-bit value
* \param f file
* \return 1 if the write was successful, 0 if not (just like fwrite()).
*/
YASM_LIB_DECL
size_t yasm_fwrite_16_b(unsigned short val, FILE *f);
/** Direct-to-file version of YASM_SAVE_32_B().
* \note Using the macro multiple times with a single fwrite() call will
* probably be faster than calling this function many times.
* \param val 32-bit value
* \param f file
* \return 1 if the write was successful, 0 if not (just like fwrite()).
*/
YASM_LIB_DECL
size_t yasm_fwrite_32_b(unsigned long val, FILE *f);
/** Read an 8-bit value from a buffer, incrementing buffer pointer.
* \note Only works properly if ptr is an (unsigned char *).
* \param ptr buffer
* \param val 8-bit value
*/
#define YASM_READ_8(val, ptr) \
(val) = *((ptr)++) & 0xFF
/** Read a 16-bit value from a buffer in little endian, incrementing buffer
* pointer.
* \note Only works properly if ptr is an (unsigned char *).
* \param ptr buffer
* \param val 16-bit value
*/
#define YASM_READ_16_L(val, ptr) \
do { \
(val) = *((ptr)++) & 0xFF; \
(val) |= (*((ptr)++) & 0xFF) << 8; \
} while (0)
/** Read a 32-bit value from a buffer in little endian, incrementing buffer
* pointer.
* \note Only works properly if ptr is an (unsigned char *).
* \param ptr buffer
* \param val 32-bit value
*/
#define YASM_READ_32_L(val, ptr) \
do { \
(val) = *((ptr)++) & 0xFF; \
(val) |= (*((ptr)++) & 0xFF) << 8; \
(val) |= (*((ptr)++) & 0xFF) << 16; \
(val) |= (*((ptr)++) & 0xFF) << 24; \
} while (0)
/** Read a 16-bit value from a buffer in big endian, incrementing buffer
* pointer.
* \note Only works properly if ptr is an (unsigned char *).
* \param ptr buffer
* \param val 16-bit value
*/
#define YASM_READ_16_B(val, ptr) \
do { \
(val) = (*((ptr)++) & 0xFF) << 8; \
(val) |= *((ptr)++) & 0xFF; \
} while (0)
/** Read a 32-bit value from a buffer in big endian, incrementing buffer
* pointer.
* \note Only works properly if ptr is an (unsigned char *).
* \param ptr buffer
* \param val 32-bit value
*/
#define YASM_READ_32_B(val, ptr) \
do { \
(val) = (*((ptr)++) & 0xFF) << 24; \
(val) |= (*((ptr)++) & 0xFF) << 16; \
(val) |= (*((ptr)++) & 0xFF) << 8; \
(val) |= *((ptr)++) & 0xFF; \
} while (0)
/** Read an 8-bit value from a buffer. Does not increment buffer pointer.
* \note Only works properly if ptr is an (unsigned char *).
* \param ptr buffer
* \param val 8-bit value
*/
#define YASM_LOAD_8(val, ptr) \
(val) = *(ptr) & 0xFF
/** Read a 16-bit value from a buffer in little endian. Does not increment
* buffer pointer.
* \note Only works properly if ptr is an (unsigned char *).
* \param ptr buffer
* \param val 16-bit value
*/
#define YASM_LOAD_16_L(val, ptr) \
do { \
(val) = *(ptr) & 0xFF; \
(val) |= (*((ptr)+1) & 0xFF) << 8; \
} while (0)
/** Read a 32-bit value from a buffer in little endian. Does not increment
* buffer pointer.
* \note Only works properly if ptr is an (unsigned char *).
* \param ptr buffer
* \param val 32-bit value
*/
#define YASM_LOAD_32_L(val, ptr) \
do { \
(val) = (unsigned long)(*(ptr) & 0xFF); \
(val) |= (unsigned long)((*((ptr)+1) & 0xFF) << 8); \
(val) |= (unsigned long)((*((ptr)+2) & 0xFF) << 16); \
(val) |= (unsigned long)((*((ptr)+3) & 0xFF) << 24); \
} while (0)
/** Read a 16-bit value from a buffer in big endian. Does not increment buffer
* pointer.
* \note Only works properly if ptr is an (unsigned char *).
* \param ptr buffer
* \param val 16-bit value
*/
#define YASM_LOAD_16_B(val, ptr) \
do { \
(val) = (*(ptr) & 0xFF) << 8; \
(val) |= *((ptr)+1) & 0xFF; \
} while (0)
/** Read a 32-bit value from a buffer in big endian. Does not increment buffer
* pointer.
* \note Only works properly if ptr is an (unsigned char *).
* \param ptr buffer
* \param val 32-bit value
*/
#define YASM_LOAD_32_B(val, ptr) \
do { \
(val) = (unsigned long)((*(ptr) & 0xFF) << 24); \
(val) |= (unsigned long)((*((ptr)+1) & 0xFF) << 16); \
(val) |= (unsigned long)((*((ptr)+2) & 0xFF) << 8); \
(val) |= (unsigned long)(*((ptr)+3) & 0xFF); \
} while (0)
#endif

View File

@ -0,0 +1,131 @@
/**
* \file libyasm/floatnum.h
* \brief YASM floating point (IEEE) interface.
*
* \license
* Copyright (C) 2001-2007 Peter Johnson
*
* Based on public-domain x86 assembly code by Randall Hyde (8/28/91).
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* - Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* - Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND OTHER CONTRIBUTORS ``AS IS''
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR OTHER CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
* \endlicense
*/
#ifndef YASM_FLOATNUM_H
#define YASM_FLOATNUM_H
#ifndef YASM_LIB_DECL
#define YASM_LIB_DECL
#endif
/** Initialize floatnum internal data structures. */
YASM_LIB_DECL
void yasm_floatnum_initialize(void);
/** Clean up internal floatnum allocations. */
YASM_LIB_DECL
void yasm_floatnum_cleanup(void);
/** Create a new floatnum from a decimal string. The input string must be in
* standard C representation ([+-]123.456e[-+]789).
* \param str floating point decimal string
* \return Newly allocated floatnum.
*/
YASM_LIB_DECL
/*@only@*/ yasm_floatnum *yasm_floatnum_create(const char *str);
/** Duplicate a floatnum.
* \param flt floatnum
* \return Newly allocated floatnum with the same value as flt.
*/
YASM_LIB_DECL
/*@only@*/ yasm_floatnum *yasm_floatnum_copy(const yasm_floatnum *flt);
/** Destroy (free allocated memory for) a floatnum.
* \param flt floatnum
*/
YASM_LIB_DECL
void yasm_floatnum_destroy(/*@only@*/ yasm_floatnum *flt);
/** Floating point calculation function: acc = acc op operand.
* \note Not all operations in yasm_expr_op may be supported; unsupported
* operations will result in an error.
* \param acc floatnum accumulator
* \param op operation
* \param operand floatnum operand
* \return Nonzero on error.
*/
YASM_LIB_DECL
int yasm_floatnum_calc(yasm_floatnum *acc, yasm_expr_op op,
yasm_floatnum *operand);
/** Convert a floatnum to single-precision and return as 32-bit value.
* The 32-bit value is a "standard" C value (eg, of unknown endian).
* \param flt floatnum
* \param ret_val pointer to storage for 32-bit output
* \return Nonzero if flt can't fit into single precision: -1 if underflow
* occurred, 1 if overflow occurred.
*/
YASM_LIB_DECL
int yasm_floatnum_get_int(const yasm_floatnum *flt,
/*@out@*/ unsigned long *ret_val);
/** Output a #yasm_floatnum to buffer in little-endian or big-endian. Puts the
* value into the least significant bits of the destination, or may be shifted
* into more significant bits by the shift parameter. The destination bits are
* cleared before being set. [0] should be the first byte output to the file.
* \note Not all sizes are valid. Currently, only 32 (single-precision), 64
* (double-precision), and 80 (extended-precision) are valid sizes.
* Use yasm_floatnum_check_size() to check for supported sizes.
* \param flt floatnum
* \param ptr pointer to storage for size bytes of output
* \param destsize destination size (in bytes)
* \param valsize size (in bits)
* \param shift left shift (in bits)
* \param bigendian endianness (nonzero=big, zero=little)
* \param warn enables standard overflow/underflow warnings
* \return Nonzero if flt can't fit into the specified precision: -1 if
* underflow occurred, 1 if overflow occurred.
*/
YASM_LIB_DECL
int yasm_floatnum_get_sized(const yasm_floatnum *flt, unsigned char *ptr,
size_t destsize, size_t valsize, size_t shift,
int bigendian, int warn);
/** Basic check to see if size is valid for flt conversion (using
* yasm_floatnum_get_sized()). Doesn't actually check for underflow/overflow
* but rather checks for size=32,64,80
* (at present).
* \param flt floatnum
* \param size number of bits of output space
* \return 1 if valid size, 0 if invalid size.
*/
YASM_LIB_DECL
int yasm_floatnum_check_size(const yasm_floatnum *flt, size_t size);
/** Print various representations of a floatnum. For debugging purposes only.
* \param f file
* \param flt floatnum
*/
YASM_LIB_DECL
void yasm_floatnum_print(const yasm_floatnum *flt, FILE *f);
#endif

View File

@ -0,0 +1,123 @@
/**
* \file libyasm/hamt.h
* \brief Hash Array Mapped Trie (HAMT) functions.
*
* \license
* Copyright (C) 2001-2007 Peter Johnson
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND OTHER CONTRIBUTORS ``AS IS''
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR OTHER CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
* \endlicense
*/
#ifndef YASM_HAMT_H
#define YASM_HAMT_H
#ifndef YASM_LIB_DECL
#define YASM_LIB_DECL
#endif
/** Hash array mapped trie data structure (opaque type). */
typedef struct HAMT HAMT;
/** Hash array mapped trie entry (opaque type). */
typedef struct HAMTEntry HAMTEntry;
/** Create new, empty, HAMT. error_func() is called when an internal error is
* encountered--it should NOT return to the calling function.
* \param nocase nonzero if HAMT should be case-insensitive
* \param error_func function called on internal error
* \return New, empty, hash array mapped trie.
*/
YASM_LIB_DECL
HAMT *HAMT_create(int nocase, /*@exits@*/ void (*error_func)
(const char *file, unsigned int line, const char *message));
/** Delete HAMT and all data associated with it. Uses deletefunc() to delete
* each data item.
* \param hamt Hash array mapped trie
* \param deletefunc Data deletion function
*/
YASM_LIB_DECL
void HAMT_destroy(/*@only@*/ HAMT *hamt,
void (*deletefunc) (/*@only@*/ void *data));
/** Insert key into HAMT, associating it with data.
* If the key is not present in the HAMT, inserts it, sets *replace to 1, and
* returns the data passed in.
* If the key is already present and *replace is 0, deletes the data passed
* in using deletefunc() and returns the data currently associated with the
* key.
* If the key is already present and *replace is 1, deletes the data currently
* associated with the key using deletefunc() and replaces it with the data
* passed in.
* \param hamt Hash array mapped trie
* \param str Key
* \param data Data to associate with key
* \param replace See above description
* \param deletefunc Data deletion function if data is replaced
* \return Data now associated with key.
*/
YASM_LIB_DECL
/*@dependent@*/ void *HAMT_insert(HAMT *hamt, /*@dependent@*/ const char *str,
/*@only@*/ void *data, int *replace,
void (*deletefunc) (/*@only@*/ void *data));
/** Search for the data associated with a key in the HAMT.
* \param hamt Hash array mapped trie
* \param str Key
* \return NULL if key/data not present in HAMT, otherwise associated data.
*/
YASM_LIB_DECL
/*@dependent@*/ /*@null@*/ void *HAMT_search(HAMT *hamt, const char *str);
/** Traverse over all keys in HAMT, calling function on each data item.
* \param hamt Hash array mapped trie
* \param d Data to pass to each call to func.
* \param func Function to call
* \return Stops early (and returns func's return value) if func returns a
* nonzero value; otherwise 0.
*/
YASM_LIB_DECL
int HAMT_traverse(HAMT *hamt, /*@null@*/ void *d,
int (*func) (/*@dependent@*/ /*@null@*/ void *node,
/*@null@*/ void *d));
/** Get the first entry in a HAMT.
* \param hamt Hash array mapped trie
* \return First entry in HAMT, or NULL if HAMT is empty.
*/
YASM_LIB_DECL
const HAMTEntry *HAMT_first(const HAMT *hamt);
/** Get the next entry in a HAMT.
* \param prev Previous entry in HAMT
* \return Next entry in HAMT, or NULL if no more entries.
*/
YASM_LIB_DECL
/*@null@*/ const HAMTEntry *HAMT_next(const HAMTEntry *prev);
/** Get the corresponding data for a HAMT entry.
* \param entry HAMT entry (as returned by HAMT_first() and HAMT_next())
* \return Corresponding data item.
*/
YASM_LIB_DECL
void *HAMTEntry_get_data(const HAMTEntry *entry);
#endif

View File

@ -0,0 +1,269 @@
/**
* \file libyasm/insn.h
* \brief YASM mnenomic instruction.
*
* \license
* Copyright (C) 2002-2007 Peter Johnson
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* - Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* - Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND OTHER CONTRIBUTORS ``AS IS''
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR OTHER CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
* \endlicense
*/
#ifndef YASM_INSN_H
#define YASM_INSN_H
#ifndef YASM_LIB_DECL
#define YASM_LIB_DECL
#endif
/** Base structure for an effective address. As with all base
* structures, must be present as the first element in any
* #yasm_arch implementation of an effective address.
*/
struct yasm_effaddr {
yasm_value disp; /**< address displacement */
/** Segment register override (0 if none). */
uintptr_t segreg;
/** 1 if length of disp must be >0. */
unsigned int need_nonzero_len:1;
/** 1 if a displacement should be present in the output. */
unsigned int need_disp:1;
/** 1 if reg*2 should not be split into reg+reg. (0 if not).
* This flag indicates (for architectures that support complex effective
* addresses such as x86) if various types of complex effective addresses
* can be split into different forms in order to minimize instruction
* length.
*/
unsigned int nosplit:1;
/** 1 if effective address is /definitely/ an effective address.
* This is used in e.g. the GAS parser to differentiate
* between "expr" (which might or might not be an effective address) and
* "expr(,1)" (which is definitely an effective address).
*/
unsigned int strong:1;
/** 1 if effective address is forced PC-relative. */
unsigned int pc_rel:1;
/** 1 if effective address is forced non-PC-relative. */
unsigned int not_pc_rel:1;
/** length of pointed data (in bytes), 0 if unknown. */
unsigned int data_len;
};
/** An instruction operand (opaque type). */
typedef struct yasm_insn_operand yasm_insn_operand;
/** The type of an instruction operand. */
typedef enum yasm_insn_operand_type {
YASM_INSN__OPERAND_REG = 1, /**< A register. */
YASM_INSN__OPERAND_SEGREG, /**< A segment register. */
YASM_INSN__OPERAND_MEMORY, /**< An effective address
* (memory reference). */
YASM_INSN__OPERAND_IMM /**< An immediate or jump target. */
} yasm_insn_operand_type;
/** An instruction operand. */
struct yasm_insn_operand {
/** Link for building linked list of operands. \internal */
/*@reldef@*/ STAILQ_ENTRY(yasm_insn_operand) link;
/** Operand data. */
union {
uintptr_t reg; /**< Arch data for reg/segreg. */
yasm_effaddr *ea; /**< Effective address for memory references. */
yasm_expr *val; /**< Value of immediate or jump target. */
} data;
yasm_expr *seg; /**< Segment expression */
uintptr_t targetmod; /**< Arch target modifier, 0 if none. */
/** Specified size of the operand, in bits. 0 if not user-specified. */
unsigned int size:16;
/** Nonzero if dereference. Used for "*foo" in GAS.
* The reason for this is that by default in GAS, an unprefixed value
* is a memory address, except for jumps/calls, in which case it needs a
* "*" prefix to become a memory address (otherwise it's an immediate).
* This isn't knowable in the parser stage, so the parser sets this flag
* to indicate the "*" prefix has been used, and the arch needs to adjust
* the operand type appropriately depending on the instruction type.
*/
unsigned int deref:1;
/** Nonzero if strict. Used for "strict foo" in NASM.
* This is used to inhibit optimization on otherwise "sized" values.
* For example, the user may just want to be explicit with the size on
* "push dword 4", but not actually want to force the immediate size to
* 4 bytes (rather wanting the optimizer to optimize it down to 1 byte as
* though "dword" was not specified). To indicate the immediate should
* actually be forced to 4 bytes, the user needs to write
* "push strict dword 4", which sets this flag.
*/
unsigned int strict:1;
/** Operand type. */
unsigned int type:4;
};
/** Base structure for "instruction" bytecodes. These are the mnenomic
* (rather than raw) representation of instructions. As with all base
* structures, must be present as the first element in any
* #yasm_arch implementation of mnenomic instruction bytecodes.
*/
struct yasm_insn {
/** Linked list of operands. */
/*@reldef@*/ STAILQ_HEAD(yasm_insn_operands, yasm_insn_operand) operands;
/** Array of prefixes. */
/*@null@*/ uintptr_t *prefixes;
/** Array of segment prefixes. */
/*@null@*/ uintptr_t *segregs;
unsigned int num_operands; /**< Number of operands. */
unsigned int num_prefixes; /**< Number of prefixes. */
unsigned int num_segregs; /**< Number of segment prefixes. */
};
/** Set segment override for an effective address.
* Some architectures (such as x86) support segment overrides on effective
* addresses. A override of an override will result in a warning.
* \param ea effective address
* \param segreg segment register (0 if none)
*/
YASM_LIB_DECL
void yasm_ea_set_segreg(yasm_effaddr *ea, uintptr_t segreg);
/** Create an instruction operand from a register.
* \param reg register
* \return Newly allocated operand.
*/
YASM_LIB_DECL
yasm_insn_operand *yasm_operand_create_reg(uintptr_t reg);
/** Create an instruction operand from a segment register.
* \param segreg segment register
* \return Newly allocated operand.
*/
YASM_LIB_DECL
yasm_insn_operand *yasm_operand_create_segreg(uintptr_t segreg);
/** Create an instruction operand from an effective address.
* \param ea effective address
* \return Newly allocated operand.
*/
YASM_LIB_DECL
yasm_insn_operand *yasm_operand_create_mem(/*@only@*/ yasm_effaddr *ea);
/** Create an instruction operand from an immediate expression.
* Looks for cases of a single register and creates a register variant of
* #yasm_insn_operand.
* \param val immediate expression
* \return Newly allocated operand.
*/
YASM_LIB_DECL
yasm_insn_operand *yasm_operand_create_imm(/*@only@*/ yasm_expr *val);
/** Get the first operand in an instruction.
* \param insn instruction
* \return First operand (NULL if no operands).
*/
yasm_insn_operand *yasm_insn_ops_first(yasm_insn *insn);
#define yasm_insn_ops_first(insn) STAILQ_FIRST(&((insn)->operands))
/** Get the next operand in an instruction.
* \param op previous operand
* \return Next operand (NULL if op was the last operand).
*/
yasm_insn_operand *yasm_insn_op_next(yasm_insn_operand *op);
#define yasm_insn_op_next(cur) STAILQ_NEXT(cur, link)
/** Add operand to the end of an instruction.
* \note Does not make a copy of the operand; so don't pass this function
* static or local variables, and discard the op pointer after calling
* this function.
* \param insn instruction
* \param op operand (may be NULL)
* \return If operand was actually appended (it wasn't NULL), the operand;
* otherwise NULL.
*/
YASM_LIB_DECL
/*@null@*/ yasm_insn_operand *yasm_insn_ops_append
(yasm_insn *insn,
/*@returned@*/ /*@null@*/ yasm_insn_operand *op);
/** Associate a prefix with an instruction.
* \param insn instruction
* \param prefix data that identifies the prefix
*/
YASM_LIB_DECL
void yasm_insn_add_prefix(yasm_insn *insn, uintptr_t prefix);
/** Associate a segment prefix with an instruction.
* \param insn instruction
* \param segreg data that identifies the segment register
*/
YASM_LIB_DECL
void yasm_insn_add_seg_prefix(yasm_insn *insn, uintptr_t segreg);
/** Initialize the common parts of an instruction.
* \internal For use by yasm_arch implementations only.
* \param insn instruction
*/
YASM_LIB_DECL
void yasm_insn_initialize(/*@out@*/ yasm_insn *insn);
/** Delete the common parts of an instruction.
* \internal For use by yasm_arch implementations only.
* \param insn instruction
* \param content if nonzero, deletes content of each operand
* \param arch architecture
*/
YASM_LIB_DECL
void yasm_insn_delete(yasm_insn *insn,
void (*ea_destroy) (/*@only@*/ yasm_effaddr *));
/** Print a list of instruction operands. For debugging purposes.
* \internal For use by yasm_arch implementations only.
* \param insn instruction
* \param f file
* \param indent_level indentation level
* \param arch architecture
*/
YASM_LIB_DECL
void yasm_insn_print(const yasm_insn *insn, FILE *f, int indent_level);
/** Finalize the common parts of an instruction.
* \internal For use by yasm_arch implementations only.
* \param insn instruction
*/
YASM_LIB_DECL
void yasm_insn_finalize(yasm_insn *insn);
#endif

View File

@ -0,0 +1,340 @@
/**
* \file libyasm/intnum.h
* \brief YASM integer number interface.
*
* \license
* Copyright (C) 2001-2007 Peter Johnson
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* - Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* - Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND OTHER CONTRIBUTORS ``AS IS''
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR OTHER CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
* \endlicense
*/
#ifndef YASM_INTNUM_H
#define YASM_INTNUM_H
#ifndef YASM_LIB_DECL
#define YASM_LIB_DECL
#endif
/** Initialize intnum internal data structures. */
YASM_LIB_DECL
void yasm_intnum_initialize(void);
/** Clean up internal intnum allocations. */
YASM_LIB_DECL
void yasm_intnum_cleanup(void);
/** Create a new intnum from a decimal string.
* \param str decimal string
* \return Newly allocated intnum.
*/
YASM_LIB_DECL
/*@only@*/ yasm_intnum *yasm_intnum_create_dec(char *str);
/** Create a new intnum from a binary string.
* \param str binary string
* \return Newly allocated intnum.
*/
YASM_LIB_DECL
/*@only@*/ yasm_intnum *yasm_intnum_create_bin(char *str);
/** Create a new intnum from an octal string.
* \param str octal string
* \return Newly allocated intnum.
*/
YASM_LIB_DECL
/*@only@*/ yasm_intnum *yasm_intnum_create_oct(char *str);
/** Create a new intnum from a hexidecimal string.
* \param str hexidecimal string
* \return Newly allocated intnum.
*/
YASM_LIB_DECL
/*@only@*/ yasm_intnum *yasm_intnum_create_hex(char *str);
/** Convert character constant to integer value, using NASM rules. NASM syntax
* supports automatic conversion from strings such as 'abcd' to a 32-bit
* integer value (little endian order). This function performs those conversions.
* \param str character constant string
* \return Newly allocated intnum.
*/
YASM_LIB_DECL
/*@only@*/ yasm_intnum *yasm_intnum_create_charconst_nasm(const char *str);
/** Convert character constant to integer value, using TASM rules. TASM syntax
* supports automatic conversion from strings such as 'abcd' to a 32-bit
* integer value (big endian order). This function performs those conversions.
* \param str character constant string
* \return Newly allocated intnum.
*/
YASM_LIB_DECL
/*@only@*/ yasm_intnum *yasm_intnum_create_charconst_tasm(const char *str);
/** Create a new intnum from an unsigned integer value.
* \param i unsigned integer value
* \return Newly allocated intnum.
*/
YASM_LIB_DECL
/*@only@*/ yasm_intnum *yasm_intnum_create_uint(unsigned long i);
/** Create a new intnum from an signed integer value.
* \param i signed integer value
* \return Newly allocated intnum.
*/
YASM_LIB_DECL
/*@only@*/ yasm_intnum *yasm_intnum_create_int(long i);
/** Create a new intnum from LEB128-encoded form.
* \param ptr pointer to start of LEB128 encoded form
* \param sign signed (1) or unsigned (0) LEB128 format
* \param size number of bytes read from ptr (output)
* \return Newly allocated intnum. Number of bytes read returned into
* bytes_read parameter.
*/
YASM_LIB_DECL
/*@only@*/ yasm_intnum *yasm_intnum_create_leb128
(const unsigned char *ptr, int sign, /*@out@*/ unsigned long *size);
/** Create a new intnum from a little-endian or big-endian buffer.
* In little endian, the LSB is in ptr[0].
* \param ptr pointer to start of buffer
* \param sign signed (1) or unsigned (0) source
* \param srcsize source buffer size (in bytes)
* \param bigendian endianness (nonzero=big, zero=little)
*/
YASM_LIB_DECL
/*@only@*/ yasm_intnum *yasm_intnum_create_sized
(unsigned char *ptr, int sign, size_t srcsize, int bigendian);
/** Duplicate an intnum.
* \param intn intnum
* \return Newly allocated intnum with the same value as intn.
*/
YASM_LIB_DECL
/*@only@*/ yasm_intnum *yasm_intnum_copy(const yasm_intnum *intn);
/** Destroy (free allocated memory for) an intnum.
* \param intn intnum
*/
YASM_LIB_DECL
void yasm_intnum_destroy(/*@only@*/ yasm_intnum *intn);
/** Floating point calculation function: acc = acc op operand.
* \note Not all operations in yasm_expr_op may be supported; unsupported
* operations will result in an error.
* \param acc intnum accumulator
* \param op operation
* \param operand intnum operand
* \return Nonzero if error occurred.
*/
YASM_LIB_DECL
int yasm_intnum_calc(yasm_intnum *acc, yasm_expr_op op, yasm_intnum *operand);
/** Compare two intnums.
* \param intn1 first intnum
* \param intn2 second intnum
* \return -1 if intn1 < intn2, 0 if intn1 == intn2, 1 if intn1 > intn2.
*/
YASM_LIB_DECL
int yasm_intnum_compare(const yasm_intnum *intn1, const yasm_intnum *intn2);
/** Zero an intnum.
* \param intn intnum
*/
YASM_LIB_DECL
void yasm_intnum_zero(yasm_intnum *intn);
/** Set an intnum to the value of another intnum.
* \param intn intnum
* \param val intnum to get value from
*/
YASM_LIB_DECL
void yasm_intnum_set(yasm_intnum *intn, const yasm_intnum *val);
/** Set an intnum to an unsigned integer.
* \param intn intnum
* \param val integer value
*/
YASM_LIB_DECL
void yasm_intnum_set_uint(yasm_intnum *intn, unsigned long val);
/** Set an intnum to an signed integer.
* \param intn intnum
* \param val integer value
*/
YASM_LIB_DECL
void yasm_intnum_set_int(yasm_intnum *intn, long val);
/** Simple value check for 0.
* \param acc intnum
* \return Nonzero if acc==0.
*/
YASM_LIB_DECL
int yasm_intnum_is_zero(const yasm_intnum *acc);
/** Simple value check for 1.
* \param acc intnum
* \return Nonzero if acc==1.
*/
YASM_LIB_DECL
int yasm_intnum_is_pos1(const yasm_intnum *acc);
/** Simple value check for -1.
* \param acc intnum
* \return Nonzero if acc==-1.
*/
YASM_LIB_DECL
int yasm_intnum_is_neg1(const yasm_intnum *acc);
/** Simple sign check.
* \param acc intnum
* \return -1 if negative, 0 if zero, +1 if positive
*/
YASM_LIB_DECL
int yasm_intnum_sign(const yasm_intnum *acc);
/** Convert an intnum to an unsigned 32-bit value. The value is in "standard"
* C format (eg, of unknown endian).
* \note Parameter intnum is truncated to fit into 32 bits. Use
* intnum_check_size() to check for overflow.
* \param intn intnum
* \return Unsigned 32-bit value of intn.
*/
YASM_LIB_DECL
unsigned long yasm_intnum_get_uint(const yasm_intnum *intn);
/** Convert an intnum to a signed 32-bit value. The value is in "standard" C
* format (eg, of unknown endian).
* \note Parameter intnum is truncated to fit into 32 bits. Use
* intnum_check_size() to check for overflow.
* \param intn intnum
* \return Signed 32-bit value of intn.
*/
YASM_LIB_DECL
long yasm_intnum_get_int(const yasm_intnum *intn);
/** Output #yasm_intnum to buffer in little-endian or big-endian. Puts the
* value into the least significant bits of the destination, or may be shifted
* into more significant bits by the shift parameter. The destination bits are
* cleared before being set. [0] should be the first byte output to the file.
* \param intn intnum
* \param ptr pointer to storage for size bytes of output
* \param destsize destination size (in bytes)
* \param valsize size (in bits)
* \param shift left shift (in bits); may be negative to specify right
* shift (standard warnings include truncation to boundary)
* \param bigendian endianness (nonzero=big, zero=little)
* \param warn enables standard warnings (value doesn't fit into valsize
* bits): <0=signed warnings, >0=unsigned warnings, 0=no warn
*/
YASM_LIB_DECL
void yasm_intnum_get_sized(const yasm_intnum *intn, unsigned char *ptr,
size_t destsize, size_t valsize, int shift,
int bigendian, int warn);
/** Check to see if intnum will fit without overflow into size bits.
* \param intn intnum
* \param size number of bits of output space
* \param rshift right shift
* \param rangetype signed/unsigned range selection:
* 0 => (0, unsigned max);
* 1 => (signed min, signed max);
* 2 => (signed min, unsigned max)
* \return Nonzero if intnum will fit.
*/
YASM_LIB_DECL
int yasm_intnum_check_size(const yasm_intnum *intn, size_t size,
size_t rshift, int rangetype);
/** Check to see if intnum will fit into a particular numeric range.
* \param intn intnum
* \param low low end of range (inclusive)
* \param high high end of range (inclusive)
* \return Nonzero if intnum is within range.
*/
YASM_LIB_DECL
int yasm_intnum_in_range(const yasm_intnum *intn, long low, long high);
/** Output #yasm_intnum to buffer in LEB128-encoded form.
* \param intn intnum
* \param ptr pointer to storage for output bytes
* \param sign signedness of LEB128 encoding (0=unsigned, 1=signed)
* \return Number of bytes generated.
*/
YASM_LIB_DECL
unsigned long yasm_intnum_get_leb128(const yasm_intnum *intn,
unsigned char *ptr, int sign);
/** Calculate number of bytes LEB128-encoded form of #yasm_intnum will take.
* \param intn intnum
* \param sign signedness of LEB128 encoding (0=unsigned, 1=signed)
* \return Number of bytes.
*/
YASM_LIB_DECL
unsigned long yasm_intnum_size_leb128(const yasm_intnum *intn, int sign);
/** Output integer to buffer in signed LEB128-encoded form.
* \param v integer
* \param ptr pointer to storage for output bytes
* \return Number of bytes generated.
*/
YASM_LIB_DECL
unsigned long yasm_get_sleb128(long v, unsigned char *ptr);
/** Calculate number of bytes signed LEB128-encoded form of integer will take.
* \param v integer
* \return Number of bytes.
*/
YASM_LIB_DECL
unsigned long yasm_size_sleb128(long v);
/** Output integer to buffer in unsigned LEB128-encoded form.
* \param v integer
* \param ptr pointer to storage for output bytes
* \return Number of bytes generated.
*/
YASM_LIB_DECL
unsigned long yasm_get_uleb128(unsigned long v, unsigned char *ptr);
/** Calculate number of bytes unsigned LEB128-encoded form of integer will take.
* \param v integer
* \return Number of bytes.
*/
YASM_LIB_DECL
unsigned long yasm_size_uleb128(unsigned long v);
/** Get an intnum as a signed decimal string. The returned string will
* contain a leading '-' if the intnum is negative.
* \param intn intnum
* \return Newly allocated string containing the decimal representation of
* the intnum.
*/
YASM_LIB_DECL
/*@only@*/ char *yasm_intnum_get_str(const yasm_intnum *intn);
/** Print an intnum. For debugging purposes.
* \param f file
* \param intn intnum
*/
YASM_LIB_DECL
void yasm_intnum_print(const yasm_intnum *intn, FILE *f);
#endif

View File

@ -0,0 +1,70 @@
#ifndef YASM_INTTREE_H
#define YASM_INTTREE_H
#ifndef YASM_LIB_DECL
#define YASM_LIB_DECL
#endif
/* The interval_tree.h and interval_tree.cc files contain code for
* interval trees implemented using red-black-trees as described in
* the book _Introduction_To_Algorithms_ by Cormen, Leisserson,
* and Rivest.
*/
typedef struct IntervalTreeNode {
struct IntervalTreeNode *left, *right, *parent;
void *data;
long low;
long high;
long maxHigh;
int red; /* if red=0 then the node is black */
} IntervalTreeNode;
typedef struct it_recursion_node {
/* This structure stores the information needed when we take the
* right branch in searching for intervals but possibly come back
* and check the left branch as well.
*/
IntervalTreeNode *start_node;
unsigned int parentIndex;
int tryRightBranch;
} it_recursion_node;
typedef struct IntervalTree {
/* A sentinel is used for root and for nil. These sentinels are
* created when ITTreeCreate is called. root->left should always
* point to the node which is the root of the tree. nil points to a
* node which should always be black but has aribtrary children and
* parent and no key or info. The point of using these sentinels is so
* that the root and nil nodes do not require special cases in the code
*/
IntervalTreeNode *root;
IntervalTreeNode *nil;
/*private:*/
unsigned int recursionNodeStackSize;
it_recursion_node * recursionNodeStack;
unsigned int currentParent;
unsigned int recursionNodeStackTop;
} IntervalTree;
YASM_LIB_DECL
IntervalTree *IT_create(void);
YASM_LIB_DECL
void IT_destroy(IntervalTree *);
YASM_LIB_DECL
void IT_print(const IntervalTree *);
YASM_LIB_DECL
void *IT_delete_node(IntervalTree *, IntervalTreeNode *, long *low,
long *high);
YASM_LIB_DECL
IntervalTreeNode *IT_insert(IntervalTree *, long low, long high, void *data);
YASM_LIB_DECL
IntervalTreeNode *IT_get_predecessor(const IntervalTree *, IntervalTreeNode *);
YASM_LIB_DECL
IntervalTreeNode *IT_get_successor(const IntervalTree *, IntervalTreeNode *);
YASM_LIB_DECL
void IT_enumerate(IntervalTree *, long low, long high, void *cbd,
void (*callback) (IntervalTreeNode *node, void *cbd));
#endif

View File

@ -0,0 +1,141 @@
/**
* \file libyasm/linemap.h
* \brief YASM virtual line mapping interface.
*
* \license
* Copyright (C) 2002-2007 Peter Johnson
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND OTHER CONTRIBUTORS ``AS IS''
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR OTHER CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
* \endlicense
*/
#ifndef YASM_LINEMAP_H
#define YASM_LINEMAP_H
#ifndef YASM_LIB_DECL
#define YASM_LIB_DECL
#endif
/** Create a new line mapping repository.
* \return New repository.
*/
YASM_LIB_DECL
yasm_linemap *yasm_linemap_create(void);
/** Clean up any memory allocated for a repository.
* \param linemap line mapping repository
*/
YASM_LIB_DECL
void yasm_linemap_destroy(yasm_linemap *linemap);
/** Get the current line position in a repository.
* \param linemap line mapping repository
* \return Current virtual line.
*/
YASM_LIB_DECL
unsigned long yasm_linemap_get_current(yasm_linemap *linemap);
/** Get bytecode and source line information, if any, for a virtual line.
* \param linemap line mapping repository
* \param line virtual line
* \param bcp pointer to return bytecode into
* \param sourcep pointer to return source code line pointer into
* \return Zero if source line information available for line, nonzero if not.
* \note If source line information is not available, bcp and sourcep targets
* are set to NULL.
*/
YASM_LIB_DECL
int yasm_linemap_get_source(yasm_linemap *linemap, unsigned long line,
/*@null@*/ yasm_bytecode **bcp,
const char **sourcep);
/** Add bytecode and source line information to the current virtual line.
* \attention Deletes any existing bytecode and source line information for
* the current virtual line.
* \param linemap line mapping repository
* \param bc bytecode (if any)
* \param source source code line
* \note The source code line pointer is NOT kept, it is strdup'ed.
*/
YASM_LIB_DECL
void yasm_linemap_add_source(yasm_linemap *linemap,
/*@null@*/ yasm_bytecode *bc,
const char *source);
/** Go to the next line (increments the current virtual line).
* \param linemap line mapping repository
* \return The current (new) virtual line.
*/
YASM_LIB_DECL
unsigned long yasm_linemap_goto_next(yasm_linemap *linemap);
/** Set a new file/line physical association starting point at the specified
* virtual line. line_inc indicates how much the "real" line is incremented
* by for each virtual line increment (0 is perfectly legal).
* \param linemap line mapping repository
* \param filename physical file name (if NULL, not changed)
* \param virtual_line virtual line number (if 0, linemap->current is used)
* \param file_line physical line number
* \param line_inc line increment
*/
YASM_LIB_DECL
void yasm_linemap_set(yasm_linemap *linemap, /*@null@*/ const char *filename,
unsigned long virtual_line, unsigned long file_line,
unsigned long line_inc);
/** Poke a single file/line association, restoring the original physical
* association starting point. Caution: increments the current virtual line
* twice.
* \param linemap line mapping repository
* \param filename physical file name (if NULL, not changed)
* \param file_line physical line number
* \return The virtual line number of the poked association.
*/
YASM_LIB_DECL
unsigned long yasm_linemap_poke(yasm_linemap *linemap,
/*@null@*/ const char *filename,
unsigned long file_line);
/** Look up the associated physical file and line for a virtual line.
* \param linemap line mapping repository
* \param line virtual line
* \param filename physical file name (output)
* \param file_line physical line number (output)
*/
YASM_LIB_DECL
void yasm_linemap_lookup(yasm_linemap *linemap, unsigned long line,
/*@out@*/ const char **filename,
/*@out@*/ unsigned long *file_line);
/** Traverses all filenames used in a linemap, calling a function on each
* filename.
* \param linemap line mapping repository
* \param d data pointer passed to func on each call
* \param func function
* \return Stops early (and returns func's return value) if func returns a
* nonzero value; otherwise 0.
*/
YASM_LIB_DECL
int yasm_linemap_traverse_filenames
(yasm_linemap *linemap, /*@null@*/ void *d,
int (*func) (const char *filename, void *d));
#endif

View File

@ -0,0 +1,124 @@
/**
* \file libyasm/listfmt.h
* \brief YASM list format interface.
*
* \license
* Copyright (C) 2004-2007 Peter Johnson
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* - Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* - Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND OTHER CONTRIBUTORS ``AS IS''
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR OTHER CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
* \endlicense
*/
#ifndef YASM_LISTFMT_H
#define YASM_LISTFMT_H
#ifndef YASM_DOXYGEN
/** Base #yasm_listfmt structure. Must be present as the first element in any
* #yasm_listfmt implementation.
*/
typedef struct yasm_listfmt_base {
/** #yasm_listfmt_module implementation for this list format. */
const struct yasm_listfmt_module *module;
} yasm_listfmt_base;
#endif
/** YASM list format module interface. */
typedef struct yasm_listfmt_module {
/** One-line description of the list format. */
const char *name;
/** Keyword used to select list format. */
const char *keyword;
/** Create list format.
* Module-level implementation of yasm_listfmt_create().
* The filenames are provided solely for informational purposes.
* \param in_filename primary input filename
* \param obj_filename object filename
* \return NULL if unable to initialize.
*/
/*@null@*/ /*@only@*/ yasm_listfmt * (*create)
(const char *in_filename, const char *obj_filename);
/** Module-level implementation of yasm_listfmt_destroy().
* Call yasm_listfmt_destroy() instead of calling this function.
*/
void (*destroy) (/*@only@*/ yasm_listfmt *listfmt);
/** Module-level implementation of yasm_listfmt_output().
* Call yasm_listfmt_output() instead of calling this function.
*/
void (*output) (yasm_listfmt *listfmt, FILE *f, yasm_linemap *linemap,
yasm_arch *arch);
} yasm_listfmt_module;
/** Get the keyword used to select a list format.
* \param listfmt list format
* \return keyword
*/
const char *yasm_listfmt_keyword(const yasm_listfmt *listfmt);
/** Initialize list format for use. Must call before any other list
* format functions. The filenames are provided solely for informational
* purposes.
* \param module list format module
* \param in_filename primary input filename
* \param obj_filename object filename
* \return NULL if object format does not provide needed support.
*/
/*@null@*/ /*@only@*/ yasm_listfmt *yasm_listfmt_create
(const yasm_listfmt_module *module, const char *in_filename,
const char *obj_filename);
/** Cleans up any allocated list format memory.
* \param listfmt list format
*/
void yasm_listfmt_destroy(/*@only@*/ yasm_listfmt *listfmt);
/** Write out list to the list file.
* This function may call all read-only yasm_* functions as necessary.
* \param listfmt list format
* \param f output list file
* \param linemap line mapping repository
* \param arch architecture
*/
void yasm_listfmt_output(yasm_listfmt *listfmt, FILE *f,
yasm_linemap *linemap, yasm_arch *arch);
#ifndef YASM_DOXYGEN
/* Inline macro implementations for listfmt functions */
#define yasm_listfmt_keyword(listfmt) \
(((yasm_listfmt_base *)listfmt)->module->keyword)
#define yasm_listfmt_create(module, in_filename, obj_filename) \
module->create(in_filename, obj_filename)
#define yasm_listfmt_destroy(listfmt) \
((yasm_listfmt_base *)listfmt)->module->destroy(listfmt)
#define yasm_listfmt_output(listfmt, f, linemap, a) \
((yasm_listfmt_base *)listfmt)->module->output(listfmt, f, linemap, a)
#endif
#endif

View File

@ -0,0 +1,32 @@
/* See md5.c for explanation and copyright information. */
#ifndef YASM_MD5_H
#define YASM_MD5_H
#ifndef YASM_LIB_DECL
#define YASM_LIB_DECL
#endif
/* Unlike previous versions of this code, uint32 need not be exactly
32 bits, merely 32 bits or more. Choosing a data type which is 32
bits instead of 64 is not important; speed is considerably more
important. ANSI guarantees that "unsigned long" will be big enough,
and always using it seems to have few disadvantages. */
typedef struct yasm_md5_context {
unsigned long buf[4];
unsigned long bits[2];
unsigned char in[64];
} yasm_md5_context;
YASM_LIB_DECL
void yasm_md5_init(yasm_md5_context *context);
YASM_LIB_DECL
void yasm_md5_update(yasm_md5_context *context, unsigned char const *buf,
unsigned long len);
YASM_LIB_DECL
void yasm_md5_final(unsigned char digest[16], yasm_md5_context *context);
YASM_LIB_DECL
void yasm_md5_transform(unsigned long buf[4], const unsigned char in[64]);
#endif /* !YASM_MD5_H */

View File

@ -0,0 +1,82 @@
/*
* YASM module loader header file
*
* Copyright (C) 2002-2007 Peter Johnson
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND OTHER CONTRIBUTORS ``AS IS''
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR OTHER CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
#ifndef YASM_MODULE_H
#define YASM_MODULE_H
#ifndef YASM_LIB_DECL
#define YASM_LIB_DECL
#endif
typedef enum yasm_module_type {
YASM_MODULE_ARCH = 0,
YASM_MODULE_DBGFMT,
YASM_MODULE_OBJFMT,
YASM_MODULE_LISTFMT,
YASM_MODULE_PARSER,
YASM_MODULE_PREPROC
} yasm_module_type;
YASM_LIB_DECL
/*@dependent@*/ /*@null@*/ void *yasm_load_module
(yasm_module_type type, const char *keyword);
#define yasm_load_arch(keyword) \
yasm_load_module(YASM_MODULE_ARCH, keyword)
#define yasm_load_dbgfmt(keyword) \
yasm_load_module(YASM_MODULE_DBGFMT, keyword)
#define yasm_load_objfmt(keyword) \
yasm_load_module(YASM_MODULE_OBJFMT, keyword)
#define yasm_load_listfmt(keyword) \
yasm_load_module(YASM_MODULE_LISTFMT, keyword)
#define yasm_load_parser(keyword) \
yasm_load_module(YASM_MODULE_PARSER, keyword)
#define yasm_load_preproc(keyword) \
yasm_load_module(YASM_MODULE_PREPROC, keyword)
YASM_LIB_DECL
void yasm_list_modules
(yasm_module_type type,
void (*printfunc) (const char *name, const char *keyword));
#define yasm_list_arch(func) \
yasm_list_modules(YASM_MODULE_ARCH, func)
#define yasm_list_dbgfmt(func) \
yasm_list_modules(YASM_MODULE_DBGFMT, func)
#define yasm_list_objfmt(func) \
yasm_list_modules(YASM_MODULE_OBJFMT, func)
#define yasm_list_listfmt(func) \
yasm_list_modules(YASM_MODULE_LISTFMT, func)
#define yasm_list_parser(func) \
yasm_list_modules(YASM_MODULE_PARSER, func)
#define yasm_list_preproc(func) \
yasm_list_modules(YASM_MODULE_PREPROC, func)
YASM_LIB_DECL
void yasm_register_module(yasm_module_type type, const char *keyword,
void *data);
#endif

View File

@ -0,0 +1,216 @@
/**
* \file libyasm/objfmt.h
* \brief YASM object format module interface.
*
* \license
* Copyright (C) 2001-2007 Peter Johnson
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* - Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* - Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND OTHER CONTRIBUTORS ``AS IS''
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR OTHER CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
* \endlicense
*/
#ifndef YASM_OBJFMT_H
#define YASM_OBJFMT_H
#ifndef YASM_DOXYGEN
/** Base #yasm_objfmt structure. Must be present as the first element in any
* #yasm_objfmt implementation.
*/
typedef struct yasm_objfmt_base {
/** #yasm_objfmt_module implementation for this object format. */
const struct yasm_objfmt_module *module;
} yasm_objfmt_base;
#endif
/** Object format module interface. */
struct yasm_objfmt_module {
/** One-line description of the object format. */
const char *name;
/** Keyword used to select object format. */
const char *keyword;
/** Default output file extension (without the '.').
* NULL means no extension, with no '.', while "" includes the '.'.
*/
/*@null@*/ const char *extension;
/** Default (starting) x86 BITS setting. This only appies to the x86
* architecture; other architectures ignore this setting.
*/
const unsigned char default_x86_mode_bits;
/** If @ signs should be legal in identifiers. */
const unsigned char id_at_ok;
/** NULL-terminated list of debug format (yasm_dbgfmt) keywords that are
* valid to use with this object format. The null debug format
* (null_dbgfmt, "null") should always be in this list so it's possible to
* have no debug output.
*/
const char **dbgfmt_keywords;
/** Default debug format keyword (set even if there's only one available to
* use).
*/
const char *default_dbgfmt_keyword;
/** NULL-terminated list of directives. NULL if none. */
/*@null@*/ const yasm_directive *directives;
/** NULL-terminated list of standard macro lookups. NULL if none. */
const yasm_stdmac *stdmacs;
/** Create object format.
* Module-level implementation of yasm_objfmt_create().
* Call yasm_objfmt_create() instead of calling this function.
* \param object object
* \param a architecture in use
* \return NULL if architecture/machine combination not supported.
*/
/*@null@*/ /*@only@*/ yasm_objfmt * (*create) (yasm_object *object);
/** Module-level implementation of yasm_objfmt_output().
* Call yasm_objfmt_output() instead of calling this function.
*/
void (*output) (yasm_object *o, FILE *f, int all_syms,
yasm_errwarns *errwarns);
/** Module-level implementation of yasm_objfmt_destroy().
* Call yasm_objfmt_destroy() instead of calling this function.
*/
void (*destroy) (/*@only@*/ yasm_objfmt *objfmt);
/** Module-level implementation of yasm_objfmt_add_default_section().
* Call yasm_objfmt_add_default_section() instead of calling this function.
*/
yasm_section * (*add_default_section) (yasm_object *object);
/** Module-level implementation of yasm_objfmt_init_new_section().
* Call yasm_objfmt_init_new_section() instead of calling this function.
*/
void (*init_new_section) (yasm_section *section, unsigned long line);
/** Module-level implementation of yasm_objfmt_section_switch().
* Call yasm_objfmt_section_switch() instead of calling this function.
*/
/*@observer@*/ /*@null@*/ yasm_section *
(*section_switch)(yasm_object *object, yasm_valparamhead *valparams,
/*@null@*/ yasm_valparamhead *objext_valparams,
unsigned long line);
/** Module-level implementation of yasm_objfmt_get_special_sym().
* Call yasm_objfmt_get_special_sym() instead of calling this function.
*/
/*@observer@*/ /*@null@*/ yasm_symrec *
(*get_special_sym)(yasm_object *object, const char *name,
const char *parser);
};
/** Create object format.
* \param module object format module
* \param object object
* \return NULL if architecture/machine combination not supported.
*/
/*@null@*/ /*@only@*/ yasm_objfmt *yasm_objfmt_create
(const yasm_objfmt_module *module, yasm_object *object);
/** Write out (post-optimized) sections to the object file.
* This function may call yasm_symrec_* functions as necessary (including
* yasm_symrec_traverse()) to retrieve symbolic information.
* \param object object
* \param f output object file
* \param all_syms if nonzero, all symbols should be included in
* the object file
* \param errwarns error/warning set
* \note Errors and warnings are stored into errwarns.
*/
void yasm_objfmt_output(yasm_object *object, FILE *f, int all_syms,
yasm_errwarns *errwarns);
/** Cleans up any allocated object format memory.
* \param objfmt object format
*/
void yasm_objfmt_destroy(/*@only@*/ yasm_objfmt *objfmt);
/** Add a default section to an object.
* \param object object
* \return Default section.
*/
yasm_section *yasm_objfmt_add_default_section(yasm_object *object);
/** Initialize the object-format specific portion of a section. Called
* by yasm_object_get_general(); in general should not be directly called.
* \param section section
* \param line virtual line (from yasm_linemap)
*/
void yasm_objfmt_init_new_section(yasm_object *object, unsigned long line);
/** Switch object file sections. The first val of the valparams should
* be the section name. Calls yasm_object_get_general() to actually get
* the section.
* \param object object
* \param valparams value/parameters
* \param objext_valparams object format-specific value/parameters
* \param line virtual line (from yasm_linemap)
* \return NULL on error, otherwise new section.
*/
/*@observer@*/ /*@null@*/ yasm_section *yasm_objfmt_section_switch
(yasm_object *object, yasm_valparamhead *valparams,
/*@null@*/ yasm_valparamhead *objext_valparams, unsigned long line);
/** Get a special symbol. Special symbols are generally used to generate
* special relocation types via the WRT mechanism.
* \param object object
* \param name symbol name (not including any parser-specific prefix)
* \param parser parser keyword
* \return NULL if unrecognized, otherwise special symbol.
*/
/*@observer@*/ /*@null@*/ yasm_symrec *yasm_objfmt_get_special_sym
(yasm_object *object, const char *name, const char *parser);
#ifndef YASM_DOXYGEN
/* Inline macro implementations for objfmt functions */
#define yasm_objfmt_create(module, object) module->create(object)
#define yasm_objfmt_output(object, f, all_syms, ews) \
((yasm_objfmt_base *)((object)->objfmt))->module->output \
(object, f, all_syms, ews)
#define yasm_objfmt_destroy(objfmt) \
((yasm_objfmt_base *)objfmt)->module->destroy(objfmt)
#define yasm_objfmt_section_switch(object, vpms, oe_vpms, line) \
((yasm_objfmt_base *)((object)->objfmt))->module->section_switch \
(object, vpms, oe_vpms, line)
#define yasm_objfmt_add_default_section(object) \
((yasm_objfmt_base *)((object)->objfmt))->module->add_default_section \
(object)
#define yasm_objfmt_init_new_section(section, line) \
((yasm_objfmt_base *)((object)->objfmt))->module->init_new_section \
(section, line)
#define yasm_objfmt_get_special_sym(object, name, parser) \
((yasm_objfmt_base *)((object)->objfmt))->module->get_special_sym \
(object, name, parser)
#endif
#endif

View File

@ -0,0 +1,67 @@
/**
* \file libyasm/parser.h
* \brief YASM parser module interface.
*
* \license
* Copyright (C) 2001-2007 Peter Johnson
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND OTHER CONTRIBUTORS ``AS IS''
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR OTHER CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
* \endlicense
*/
#ifndef YASM_PARSER_H
#define YASM_PARSER_H
/** YASM parser module interface. The "front end" of the assembler. */
typedef struct yasm_parser_module {
/** One-line description of the parser */
const char *name;
/** Keyword used to select parser on the command line */
const char *keyword;
/** NULL-terminated list of preprocessors that are valid to use with this
* parser. The raw preprocessor (raw_preproc) should always be in this
* list so it's always possible to have no preprocessing done.
*/
const char **preproc_keywords;
/** Default preprocessor. */
const char *default_preproc_keyword;
/** NULL-terminated list of standard macro lookups. NULL if none. */
const yasm_stdmac *stdmacs;
/** Parse a source file into an object.
* \param object object to parse into (already created)
* \param pp preprocessor
* \param save_input nonzero if the parser should save the original
* lines of source into the object's linemap (via
* yasm_linemap_add_data()).
* \param errwarns error/warning set
* \note Parse errors and warnings are stored into errwarns.
*/
void (*do_parse)
(yasm_object *object, yasm_preproc *pp, int save_input,
yasm_linemap *linemap, yasm_errwarns *errwarns);
} yasm_parser_module;
#endif

View File

@ -0,0 +1,19 @@
/* Modified for use with yasm by Peter Johnson. */
/*
------------------------------------------------------------------------------
By Bob Jenkins, September 1996.
lookupa.h, a hash function for table lookup, same function as lookup.c.
Use this code in any way you wish. Public Domain. It has no warranty.
Source is http://burtleburtle.net/bob/c/lookupa.h
------------------------------------------------------------------------------
*/
#ifndef YASM_LIB_DECL
#define YASM_LIB_DECL
#endif
YASM_LIB_DECL
unsigned long phash_lookup(const char *k, size_t length,
unsigned long level);
YASM_LIB_DECL
void phash_checksum(const char *k, size_t length, unsigned long *state);

View File

@ -0,0 +1,210 @@
/**
* \file libyasm/preproc.h
* \brief YASM preprocessor module interface.
*
* \license
* Copyright (C) 2001-2007 Peter Johnson
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND OTHER CONTRIBUTORS ``AS IS''
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR OTHER CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
* \endlicense
*/
#ifndef YASM_PREPROC_H
#define YASM_PREPROC_H
#ifndef YASM_DOXYGEN
/** Base #yasm_preproc structure. Must be present as the first element in any
* #yasm_preproc implementation.
*/
typedef struct yasm_preproc_base {
/** #yasm_preproc_module implementation for this preprocessor. */
const struct yasm_preproc_module *module;
} yasm_preproc_base;
#endif
/** YASM preprocesor module interface. */
typedef struct yasm_preproc_module {
/** One-line description of the preprocessor. */
const char *name;
/** Keyword used to select preprocessor on the command line. */
const char *keyword;
/** Create preprocessor.
* Module-level implementation of yasm_preproc_create().
* Call yasm_preproc_create() instead of calling this function.
*
* \param in_filename initial starting filename, or "-" to read from
* stdin
* \param symtab symbol table (may be NULL if none)
* \param lm line mapping repository
* \param errwarns error/warnning set.
* \return New preprocessor.
*
* \note Any preprocessor errors and warnings are stored into errwarns.
*/
/*@only@*/ yasm_preproc * (*create) (const char *in_filename,
yasm_symtab *symtab,
yasm_linemap *lm,
yasm_errwarns *errwarns);
/** Module-level implementation of yasm_preproc_destroy().
* Call yasm_preproc_destroy() instead of calling this function.
*/
void (*destroy) (/*@only@*/ yasm_preproc *preproc);
/** Module-level implementation of yasm_preproc_get_line().
* Call yasm_preproc_get_line() instead of calling this function.
*/
char * (*get_line) (yasm_preproc *preproc);
/** Module-level implementation of yasm_preproc_get_included_file().
* Call yasm_preproc_get_included_file() instead of calling this function.
*/
size_t (*get_included_file) (yasm_preproc *preproc, /*@out@*/ char *buf,
size_t max_size);
/** Module-level implementation of yasm_preproc_add_include_file().
* Call yasm_preproc_add_include_file() instead of calling this function.
*/
void (*add_include_file) (yasm_preproc *preproc, const char *filename);
/** Module-level implementation of yasm_preproc_predefine_macro().
* Call yasm_preproc_predefine_macro() instead of calling this function.
*/
void (*predefine_macro) (yasm_preproc *preproc, const char *macronameval);
/** Module-level implementation of yasm_preproc_undefine_macro().
* Call yasm_preproc_undefine_macro() instead of calling this function.
*/
void (*undefine_macro) (yasm_preproc *preproc, const char *macroname);
/** Module-level implementation of yasm_preproc_builtin_define().
* Call yasm_preproc_builtin_define() instead of calling this function.
*/
void (*define_builtin) (yasm_preproc *preproc, const char *macronameval);
/** Module-level implementation of yasm_preproc_add_standard().
* Call yasm_preproc_add_standard() instead of calling this function.
*/
void (*add_standard) (yasm_preproc *preproc, const char **macros);
} yasm_preproc_module;
/** Initialize preprocessor.
* The preprocessor needs access to the object format module to find out
* any output format specific macros.
* \param module preprocessor module
* \param in_filename initial starting filename, or "-" to read from stdin
* \param symtab symbol table (may be NULL if none)
* \param lm line mapping repository
* \param errwarns error/warning set
* \return New preprocessor.
* \note Errors/warnings are stored into errwarns.
*/
/*@only@*/ yasm_preproc *yasm_preproc_create
(yasm_preproc_module *module, const char *in_filename,
yasm_symtab *symtab, yasm_linemap *lm, yasm_errwarns *errwarns);
/** Cleans up any allocated preproc memory.
* \param preproc preprocessor
*/
void yasm_preproc_destroy(/*@only@*/ yasm_preproc *preproc);
/** Gets a single line of preprocessed source code.
* \param preproc preprocessor
* \return Allocated line of code, without the trailing \n.
*/
char *yasm_preproc_get_line(yasm_preproc *preproc);
/** Get the next filename included by the source code.
* \param preproc preprocessor
* \param buf destination buffer for filename
* \param max_size maximum number of bytes that can be returned in buf
* \return Actual number of bytes returned in buf.
*/
size_t yasm_preproc_get_included_file(yasm_preproc *preproc,
/*@out@*/ char *buf, size_t max_size);
/** Pre-include a file.
* \param preproc preprocessor
* \param filename filename
*/
void yasm_preproc_add_include_file(yasm_preproc *preproc,
const char *filename);
/** Pre-define a macro.
* \param preproc preprocessor
* \param macronameval "name=value" string
*/
void yasm_preproc_predefine_macro(yasm_preproc *preproc,
const char *macronameval);
/** Un-define a macro.
* \param preproc preprocessor
* \param macroname macro name
*/
void yasm_preproc_undefine_macro(yasm_preproc *preproc, const char *macroname);
/** Define a builtin macro, preprocessed before the "standard" macros.
* \param preproc preprocessor
* \param macronameval "name=value" string
*/
void yasm_preproc_define_builtin(yasm_preproc *preproc,
const char *macronameval);
/** Define additional standard macros, preprocessed after the builtins but
* prior to any user-defined macros.
* \param preproc preprocessor
* \param macros NULL-terminated array of macro strings
*/
void yasm_preproc_add_standard(yasm_preproc *preproc,
const char **macros);
#ifndef YASM_DOXYGEN
/* Inline macro implementations for preproc functions */
#define yasm_preproc_create(module, in_filename, symtab, lm, ews) \
module->create(in_filename, symtab, lm, ews)
#define yasm_preproc_destroy(preproc) \
((yasm_preproc_base *)preproc)->module->destroy(preproc)
#define yasm_preproc_get_line(preproc) \
((yasm_preproc_base *)preproc)->module->get_line(preproc)
#define yasm_preproc_get_included_file(preproc, buf, max_size) \
((yasm_preproc_base *)preproc)->module->get_included_file(preproc, buf, max_size)
#define yasm_preproc_add_include_file(preproc, filename) \
((yasm_preproc_base *)preproc)->module->add_include_file(preproc, filename)
#define yasm_preproc_predefine_macro(preproc, macronameval) \
((yasm_preproc_base *)preproc)->module->predefine_macro(preproc, \
macronameval)
#define yasm_preproc_undefine_macro(preproc, macroname) \
((yasm_preproc_base *)preproc)->module->undefine_macro(preproc, macroname)
#define yasm_preproc_define_builtin(preproc, macronameval) \
((yasm_preproc_base *)preproc)->module->define_builtin(preproc, \
macronameval)
#define yasm_preproc_add_standard(preproc, macros) \
((yasm_preproc_base *)preproc)->module->add_standard(preproc, \
macros)
#endif
#endif

View File

@ -0,0 +1,383 @@
/**
* \file libyasm/section.h
* \brief YASM section interface.
*
* \license
* Copyright (C) 2001-2007 Peter Johnson
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* - Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* - Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND OTHER CONTRIBUTORS ``AS IS''
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR OTHER CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
* \endlicense
*/
#ifndef YASM_SECTION_H
#define YASM_SECTION_H
#ifndef YASM_LIB_DECL
#define YASM_LIB_DECL
#endif
/** Basic YASM relocation. Object formats will need to extend this
* structure with additional fields for relocation type, etc.
*/
typedef struct yasm_reloc yasm_reloc;
struct yasm_reloc {
/*@reldef@*/ STAILQ_ENTRY(yasm_reloc) link; /**< Link to next reloc */
yasm_intnum *addr; /**< Offset (address) within section */
/*@dependent@*/ yasm_symrec *sym; /**< Relocated symbol */
};
/** An object. This is the internal representation of an object file. */
struct yasm_object {
/*@owned@*/ char *src_filename; /**< Source filename */
/*@owned@*/ char *obj_filename; /**< Object filename */
/*@owned@*/ yasm_symtab *symtab; /**< Symbol table */
/*@owned@*/ yasm_arch *arch; /**< Target architecture */
/*@owned@*/ yasm_objfmt *objfmt; /**< Object format */
/*@owned@*/ yasm_dbgfmt *dbgfmt; /**< Debug format */
/** Currently active section. Used by some directives. NULL if no
* section active.
*/
/*@dependent@*/ /*@null@*/ yasm_section *cur_section;
/** Linked list of sections. */
/*@reldef@*/ STAILQ_HEAD(yasm_sectionhead, yasm_section) sections;
/** Directives, organized as two level HAMT; first level is parser,
* second level is directive name.
*/
/*@owned@*/ struct HAMT *directives;
/** Prefix prepended to externally-visible symbols (empty string if none) */
/*@owned@*/ char *global_prefix;
/** Suffix appended to externally-visible symbols (empty string if none) */
/*@owned@*/ char *global_suffix;
};
/** Create a new object. A default section is created as the first section.
* An empty symbol table (yasm_symtab) and line mapping (yasm_linemap) are
* automatically created.
* \param src_filename source filename (e.g. "file.asm")
* \param obj_filename object filename (e.g. "file.o")
* \param arch architecture
* \param objfmt_module object format module
* \param dbgfmt_module debug format module
* \return Newly allocated object, or NULL on error.
*/
YASM_LIB_DECL
/*@null@*/ /*@only@*/ yasm_object *yasm_object_create
(const char *src_filename, const char *obj_filename,
/*@kept@*/ yasm_arch *arch,
const yasm_objfmt_module *objfmt_module,
const yasm_dbgfmt_module *dbgfmt_module);
/** Create a new, or continue an existing, general section. The section is
* added to the object if there's not already a section by that name.
* \param object object
* \param name section name
* \param align alignment in bytes (0 if none)
* \param code if nonzero, section is intended to contain code
* (e.g. alignment should be made with NOP instructions, not 0)
* \param res_only if nonzero, only space-reserving bytecodes are allowed in
* the section (ignored if section already exists)
* \param isnew output; set to nonzero if section did not already exist
* \param line virtual line of section declaration (ignored if section
* already exists)
* \return New section.
*/
YASM_LIB_DECL
/*@dependent@*/ yasm_section *yasm_object_get_general
(yasm_object *object, const char *name, unsigned long align, int code,
int res_only, /*@out@*/ int *isnew, unsigned long line);
/** Handle a directive. Passed down to object format, debug format, or
* architecture as appropriate.
* \param object object
* \param name directive name
* \param parser parser keyword
* \param valparams value/parameters
* \param objext_valparams "object format-specific" value/parameters
* \param line virtual line (from yasm_linemap)
* \return 0 if directive recognized, nonzero if unrecognized.
*/
YASM_LIB_DECL
int yasm_object_directive(yasm_object *object, const char *name,
const char *parser, yasm_valparamhead *valparams,
yasm_valparamhead *objext_valparams,
unsigned long line);
/** Delete (free allocated memory for) an object. All sections in the
* object and all bytecodes within those sections are also deleted.
* \param object object
*/
YASM_LIB_DECL
void yasm_object_destroy(/*@only@*/ yasm_object *object);
/** Print an object. For debugging purposes.
* \param object object
* \param f file
* \param indent_level indentation level
*/
YASM_LIB_DECL
void yasm_object_print(const yasm_object *object, FILE *f, int indent_level);
/** Finalize an object after parsing.
* \param object object
* \param errwarns error/warning set
* \note Errors/warnings are stored into errwarns.
*/
YASM_LIB_DECL
void yasm_object_finalize(yasm_object *object, yasm_errwarns *errwarns);
/** Traverses all sections in an object, calling a function on each section.
* \param object object
* \param d data pointer passed to func on each call
* \param func function
* \return Stops early (and returns func's return value) if func returns a
* nonzero value; otherwise 0.
*/
YASM_LIB_DECL
int yasm_object_sections_traverse
(yasm_object *object, /*@null@*/ void *d,
int (*func) (yasm_section *sect, /*@null@*/ void *d));
/** Find a general section in an object, based on its name.
* \param object object
* \param name section name
* \return Section matching name, or NULL if no match found.
*/
YASM_LIB_DECL
/*@dependent@*/ /*@null@*/ yasm_section *yasm_object_find_general
(yasm_object *object, const char *name);
/** Change the source filename for an object.
* \param object object
* \param src_filename new source filename (e.g. "file.asm")
*/
YASM_LIB_DECL
void yasm_object_set_source_fn(yasm_object *object, const char *src_filename);
/** Change the prefix used for externally-visible symbols.
* \param object object
* \param prefix new prefix
*/
YASM_LIB_DECL
void yasm_object_set_global_prefix(yasm_object *object, const char *prefix);
/** Change the suffix used for externally-visible symbols.
* \param object object
* \param suffix new suffix
*/
YASM_LIB_DECL
void yasm_object_set_global_suffix(yasm_object *object, const char *suffix);
/** Optimize an object. Takes the unoptimized object and optimizes it.
* If successful, the object is ready for output to an object file.
* \param object object
* \param errwarns error/warning set
* \note Optimization failures are stored into errwarns.
*/
YASM_LIB_DECL
void yasm_object_optimize(yasm_object *object, yasm_errwarns *errwarns);
/** Determine if a section is flagged to contain code.
* \param sect section
* \return Nonzero if section is flagged to contain code.
*/
YASM_LIB_DECL
int yasm_section_is_code(yasm_section *sect);
/** Get yasm_optimizer-specific flags. For yasm_optimizer use only.
* \param sect section
* \return Optimizer-specific flags.
*/
YASM_LIB_DECL
unsigned long yasm_section_get_opt_flags(const yasm_section *sect);
/** Set yasm_optimizer-specific flags. For yasm_optimizer use only.
* \param sect section
* \param opt_flags optimizer-specific flags.
*/
YASM_LIB_DECL
void yasm_section_set_opt_flags(yasm_section *sect, unsigned long opt_flags);
/** Determine if a section was declared as the "default" section (e.g. not
* created through a section directive).
* \param sect section
* \return Nonzero if section was declared as default.
*/
YASM_LIB_DECL
int yasm_section_is_default(const yasm_section *sect);
/** Set section "default" flag to a new value.
* \param sect section
* \param def new value of default flag
*/
YASM_LIB_DECL
void yasm_section_set_default(yasm_section *sect, int def);
/** Get object owner of a section.
* \param sect section
* \return Object this section is a part of.
*/
YASM_LIB_DECL
yasm_object *yasm_section_get_object(const yasm_section *sect);
/** Get assocated data for a section and data callback.
* \param sect section
* \param callback callback used when adding data
* \return Associated data (NULL if none).
*/
YASM_LIB_DECL
/*@dependent@*/ /*@null@*/ void *yasm_section_get_data
(yasm_section *sect, const yasm_assoc_data_callback *callback);
/** Add associated data to a section.
* \attention Deletes any existing associated data for that data callback.
* \param sect section
* \param callback callback
* \param data data to associate
*/
YASM_LIB_DECL
void yasm_section_add_data(yasm_section *sect,
const yasm_assoc_data_callback *callback,
/*@null@*/ /*@only@*/ void *data);
/** Add a relocation to a section.
* \param sect section
* \param reloc relocation
* \param destroy_func function that can destroy the relocation
* \note Does not make a copy of reloc. The same destroy_func must be
* used for all relocations in a section or an internal error will occur.
* The section will destroy the relocation address; it is the caller's
* responsibility to destroy any other allocated data.
*/
YASM_LIB_DECL
void yasm_section_add_reloc(yasm_section *sect, yasm_reloc *reloc,
void (*destroy_func) (/*@only@*/ void *reloc));
/** Get the first relocation for a section.
* \param sect section
* \return First relocation for section. NULL if no relocations.
*/
YASM_LIB_DECL
/*@null@*/ yasm_reloc *yasm_section_relocs_first(yasm_section *sect);
/** Get the next relocation for a section.
* \param reloc previous relocation
* \return Next relocation for section. NULL if no more relocations.
*/
/*@null@*/ yasm_reloc *yasm_section_reloc_next(yasm_reloc *reloc);
#ifndef YASM_DOXYGEN
#define yasm_section_reloc_next(x) STAILQ_NEXT((x), link)
#endif
/** Get the basic relocation information for a relocation.
* \param reloc relocation
* \param addrp address of relocation within section (returned)
* \param symp relocated symbol (returned)
*/
YASM_LIB_DECL
void yasm_reloc_get(yasm_reloc *reloc, yasm_intnum **addrp,
/*@dependent@*/ yasm_symrec **symp);
/** Get the first bytecode in a section.
* \param sect section
* \return First bytecode in section (at least one empty bytecode is always
* present).
*/
YASM_LIB_DECL
yasm_bytecode *yasm_section_bcs_first(yasm_section *sect);
/** Get the last bytecode in a section.
* \param sect section
* \return Last bytecode in section (at least one empty bytecode is always
* present).
*/
YASM_LIB_DECL
yasm_bytecode *yasm_section_bcs_last(yasm_section *sect);
/** Add bytecode to the end of a section.
* \note Does not make a copy of bc; so don't pass this function static or
* local variables, and discard the bc pointer after calling this
* function.
* \param sect section
* \param bc bytecode (may be NULL)
* \return If bytecode was actually appended (it wasn't NULL or empty), the
* bytecode; otherwise NULL.
*/
YASM_LIB_DECL
/*@only@*/ /*@null@*/ yasm_bytecode *yasm_section_bcs_append
(yasm_section *sect,
/*@returned@*/ /*@only@*/ /*@null@*/ yasm_bytecode *bc);
/** Traverses all bytecodes in a section, calling a function on each bytecode.
* \param sect section
* \param errwarns error/warning set (may be NULL)
* \param d data pointer passed to func on each call (may be NULL)
* \param func function
* \return Stops early (and returns func's return value) if func returns a
* nonzero value; otherwise 0.
* \note If errwarns is non-NULL, yasm_errwarn_propagate() is called after
* each call to func (with the bytecode's line number).
*/
YASM_LIB_DECL
int yasm_section_bcs_traverse
(yasm_section *sect, /*@null@*/ yasm_errwarns *errwarns,
/*@null@*/ void *d, int (*func) (yasm_bytecode *bc, /*@null@*/ void *d));
/** Get name of a section.
* \param sect section
* \return Section name.
*/
YASM_LIB_DECL
/*@observer@*/ const char *yasm_section_get_name(const yasm_section *sect);
/** Change alignment of a section.
* \param sect section
* \param align alignment in bytes
* \param line virtual line
*/
YASM_LIB_DECL
void yasm_section_set_align(yasm_section *sect, unsigned long align,
unsigned long line);
/** Get alignment of a section.
* \param sect section
* \return Alignment in bytes (0 if none).
*/
YASM_LIB_DECL
unsigned long yasm_section_get_align(const yasm_section *sect);
/** Print a section. For debugging purposes.
* \param f file
* \param indent_level indentation level
* \param sect section
* \param print_bcs if nonzero, print bytecodes within section
*/
YASM_LIB_DECL
void yasm_section_print(/*@null@*/ const yasm_section *sect, FILE *f,
int indent_level, int print_bcs);
#endif

View File

@ -0,0 +1,437 @@
/**
* \file libyasm/symrec.h
* \brief YASM symbol table interface.
*
* \license
* Copyright (C) 2001-2007 Michael Urman, Peter Johnson
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* - Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* - Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND OTHER CONTRIBUTORS ``AS IS''
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR OTHER CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
* \endlicense
*/
#ifndef YASM_SYMREC_H
#define YASM_SYMREC_H
#ifndef YASM_LIB_DECL
#define YASM_LIB_DECL
#endif
/** Symbol status. YASM_SYM_DEFINED is set by yasm_symtab_define_label(),
* yasm_symtab_define_equ(), or yasm_symtab_declare()/yasm_symrec_declare()
* with a visibility of #YASM_SYM_EXTERN or #YASM_SYM_COMMON.
*/
typedef enum yasm_sym_status {
YASM_SYM_NOSTATUS = 0, /**< no status */
YASM_SYM_USED = 1 << 0, /**< for use before definition */
YASM_SYM_DEFINED = 1 << 1, /**< once it's been defined in the file */
YASM_SYM_VALUED = 1 << 2, /**< once its value has been determined */
YASM_SYM_NOTINTABLE = 1 << 3 /**< if it's not in sym_table (ex. '$') */
} yasm_sym_status;
/** Symbol record visibility.
* \note YASM_SYM_EXTERN and YASM_SYM_COMMON are mutually exclusive.
*/
typedef enum yasm_sym_vis {
YASM_SYM_LOCAL = 0, /**< Default, local only */
YASM_SYM_GLOBAL = 1 << 0, /**< If symbol is declared GLOBAL */
YASM_SYM_COMMON = 1 << 1, /**< If symbol is declared COMMON */
YASM_SYM_EXTERN = 1 << 2, /**< If symbol is declared EXTERN */
YASM_SYM_DLOCAL = 1 << 3 /**< If symbol is explicitly declared LOCAL */
} yasm_sym_vis;
/** Create a new symbol table. */
YASM_LIB_DECL
yasm_symtab *yasm_symtab_create(void);
/** Destroy a symbol table and all internal symbols.
* \param symtab symbol table
* \warning All yasm_symrec *'s into this symbol table become invalid after
* this is called!
*/
YASM_LIB_DECL
void yasm_symtab_destroy(/*@only@*/ yasm_symtab *symtab);
/** Set the symbol table to be case sensitive or not.
* Should be called before adding any symbol.
* \param symtab symbol table
* \param sensitive whether the symbol table should be case sensitive.
*/
YASM_LIB_DECL
void yasm_symtab_set_case_sensitive(yasm_symtab *symtab, int sensitive);
/** Get a reference to the symbol table's "absolute" symbol. This is
* essentially an EQU with no name and value 0, and is used for relocating
* absolute current-position-relative values.
* \see yasm_value_set_curpos_rel().
* \param symtab symbol table
* \return Absolute symbol (dependent pointer, do not free).
*/
YASM_LIB_DECL
/*@dependent@*/ yasm_symrec *yasm_symtab_abs_sym(yasm_symtab *symtab);
/** Get a reference to (use) a symbol. The symbol does not necessarily need to
* be defined before it is used.
* \param symtab symbol table
* \param name symbol name
* \param line virtual line where referenced
* \return Symbol (dependent pointer, do not free).
*/
YASM_LIB_DECL
/*@dependent@*/ yasm_symrec *yasm_symtab_use
(yasm_symtab *symtab, const char *name, unsigned long line);
/** Get a reference to a symbol, without "using" it. Should be used for cases
* when an internal assembler usage of a symbol shouldn't be treated like a
* normal user symbol usage.
* \param symtab symbol table
* \param name symbol name
* \return Symbol (dependent pointer, do not free). May be NULL if symbol
* doesn't exist.
*/
YASM_LIB_DECL
/*@null@*/ /*@dependent@*/ yasm_symrec *yasm_symtab_get
(yasm_symtab *symtab, const char *name);
/** Define a symbol as an EQU value.
* \param symtab symbol table
* \param name symbol (EQU) name
* \param e EQU value (expression)
* \param line virtual line of EQU
* \return Symbol (dependent pointer, do not free).
*/
YASM_LIB_DECL
/*@dependent@*/ yasm_symrec *yasm_symtab_define_equ
(yasm_symtab *symtab, const char *name, /*@keep@*/ yasm_expr *e,
unsigned long line);
/** Define a symbol as a label.
* \param symtab symbol table
* \param name symbol (label) name
* \param precbc bytecode preceding label
* \param in_table nonzero if the label should be inserted into the symbol
* table (some specially-generated ones should not be)
* \param line virtual line of label
* \return Symbol (dependent pointer, do not free).
*/
YASM_LIB_DECL
/*@dependent@*/ yasm_symrec *yasm_symtab_define_label
(yasm_symtab *symtab, const char *name,
/*@dependent@*/ yasm_bytecode *precbc, int in_table, unsigned long line);
/** Define a symbol as a label representing the current assembly position.
* This should be used for this purpose instead of yasm_symtab_define_label()
* as value_finalize_scan() looks for usage of this symbol type for special
* handling. The symbol created is not inserted into the symbol table.
* \param symtab symbol table
* \param name symbol (label) name
* \param precbc bytecode preceding label
* \param line virtual line of label
* \return Symbol (dependent pointer, do not free).
*/
YASM_LIB_DECL
/*@dependent@*/ yasm_symrec *yasm_symtab_define_curpos
(yasm_symtab *symtab, const char *name,
/*@dependent@*/ yasm_bytecode *precbc, unsigned long line);
/** Define a special symbol that will appear in the symbol table and have a
* defined name, but have no other data associated with it within the
* standard symrec.
* \param symtab symbol table
* \param name symbol name
* \param vis symbol visibility
* \return Symbol (dependent pointer, do not free).
*/
YASM_LIB_DECL
/*@dependent@*/ yasm_symrec *yasm_symtab_define_special
(yasm_symtab *symtab, const char *name, yasm_sym_vis vis);
/** Declare external visibility of a symbol.
* \note Not all visibility combinations are allowed.
* \param symtab symbol table
* \param name symbol name
* \param vis visibility
* \param line virtual line of visibility-setting
* \return Symbol (dependent pointer, do not free).
*/
YASM_LIB_DECL
/*@dependent@*/ yasm_symrec *yasm_symtab_declare
(yasm_symtab *symtab, const char *name, yasm_sym_vis vis,
unsigned long line);
/** Declare external visibility of a symbol.
* \note Not all visibility combinations are allowed.
* \param symrec symbol
* \param vis visibility
* \param line virtual line of visibility-setting
*/
YASM_LIB_DECL
void yasm_symrec_declare(yasm_symrec *symrec, yasm_sym_vis vis,
unsigned long line);
/** Callback function for yasm_symrec_traverse().
* \param sym symbol
* \param d data passed into yasm_symrec_traverse()
* \return Nonzero to stop symbol traversal.
*/
typedef int (*yasm_symtab_traverse_callback)
(yasm_symrec *sym, /*@null@*/ void *d);
/** Traverse all symbols in the symbol table.
* \param symtab symbol table
* \param d data to pass to each call of callback function
* \param func callback function called on each symbol
* \return Nonzero value returned by callback function if it ever returned
* nonzero.
*/
YASM_LIB_DECL
int /*@alt void@*/ yasm_symtab_traverse
(yasm_symtab *symtab, /*@null@*/ void *d,
yasm_symtab_traverse_callback func);
/** Symbol table iterator (opaque type). */
typedef struct yasm_symtab_iter yasm_symtab_iter;
/** Get an iterator pointing to the first symbol in the symbol table.
* \param symtab symbol table
* \return Iterator for the symbol table.
*/
YASM_LIB_DECL
const yasm_symtab_iter *yasm_symtab_first(const yasm_symtab *symtab);
/** Move a symbol table iterator to the next symbol in the symbol table.
* \param prev Previous iterator value
* \return Next iterator value, or NULL if no more symbols in the table.
*/
YASM_LIB_DECL
/*@null@*/ const yasm_symtab_iter *yasm_symtab_next
(const yasm_symtab_iter *prev);
/** Get the symbol corresponding to the current symbol table iterator value.
* \param cur iterator value
* \return Corresponding symbol.
*/
YASM_LIB_DECL
yasm_symrec *yasm_symtab_iter_value(const yasm_symtab_iter *cur);
/** Finalize symbol table after parsing stage. Checks for symbols that are
* used but never defined or declared #YASM_SYM_EXTERN or #YASM_SYM_COMMON.
* \param symtab symbol table
* \param undef_extern if nonzero, all undef syms should be declared extern
* \param errwarns error/warning set
* \note Errors/warnings are stored into errwarns.
*/
YASM_LIB_DECL
void yasm_symtab_parser_finalize(yasm_symtab *symtab, int undef_extern,
yasm_errwarns *errwarns);
/** Print the symbol table. For debugging purposes.
* \param symtab symbol table
* \param f file
* \param indent_level indentation level
*/
YASM_LIB_DECL
void yasm_symtab_print(yasm_symtab *symtab, FILE *f, int indent_level);
/** Get the name of a symbol.
* \param sym symbol
* \return Symbol name.
*/
YASM_LIB_DECL
/*@observer@*/ const char *yasm_symrec_get_name(const yasm_symrec *sym);
/** Get the externally-visible (global) name of a symbol.
* \param sym symbol
* \param object object
* \return Externally-visible symbol name (allocated, caller must free).
*/
YASM_LIB_DECL
/*@only@*/ char *yasm_symrec_get_global_name(const yasm_symrec *sym,
const yasm_object *object);
/** Get the visibility of a symbol.
* \param sym symbol
* \return Symbol visibility.
*/
YASM_LIB_DECL
yasm_sym_vis yasm_symrec_get_visibility(const yasm_symrec *sym);
/** Get the status of a symbol.
* \param sym symbol
* \return Symbol status.
*/
YASM_LIB_DECL
yasm_sym_status yasm_symrec_get_status(const yasm_symrec *sym);
/** Get the virtual line of where a symbol was first defined.
* \param sym symbol
* \return line virtual line
*/
YASM_LIB_DECL
unsigned long yasm_symrec_get_def_line(const yasm_symrec *sym);
/** Get the virtual line of where a symbol was first declared.
* \param sym symbol
* \return line virtual line
*/
YASM_LIB_DECL
unsigned long yasm_symrec_get_decl_line(const yasm_symrec *sym);
/** Get the virtual line of where a symbol was first used.
* \param sym symbol
* \return line virtual line
*/
YASM_LIB_DECL
unsigned long yasm_symrec_get_use_line(const yasm_symrec *sym);
/** Get EQU value of a symbol.
* \param sym symbol
* \return EQU value, or NULL if symbol is not an EQU or is not defined.
*/
YASM_LIB_DECL
/*@observer@*/ /*@null@*/ const yasm_expr *yasm_symrec_get_equ
(const yasm_symrec *sym);
/** Dependent pointer to a bytecode. */
typedef /*@dependent@*/ yasm_bytecode *yasm_symrec_get_label_bytecodep;
/** Get the label location of a symbol.
* \param sym symbol
* \param precbc bytecode preceding label (output)
* \return 0 if not symbol is not a label or if the symbol's visibility is
* #YASM_SYM_EXTERN or #YASM_SYM_COMMON (not defined in the file).
*/
YASM_LIB_DECL
int yasm_symrec_get_label(const yasm_symrec *sym,
/*@out@*/ yasm_symrec_get_label_bytecodep *precbc);
/** Set the size of a symbol.
* \param sym symbol
* \param size size to be set
*/
YASM_LIB_DECL
void yasm_symrec_set_size(yasm_symrec *sym, int size);
/** Get the size of a symbol.
* \param sym symbol
* \return size of the symbol, 0 if none specified by the user.
*/
YASM_LIB_DECL
int yasm_symrec_get_size(const yasm_symrec *sym);
/** Set the segment of a symbol.
* \param sym symbol
* \param segment segment to be set
*/
YASM_LIB_DECL
void yasm_symrec_set_segment(yasm_symrec *sym, const char *segment);
/** Get the segment of a symbol.
* \param sym symbol
* \return segment of the symbol, NULL if none specified by the user.
*/
YASM_LIB_DECL
const char *yasm_symrec_get_segment(const yasm_symrec *sym);
/** Determine if symbol is the "absolute" symbol created by
* yasm_symtab_abs_sym().
* \param sym symbol
* \return 0 if symbol is not the "absolute" symbol, nonzero otherwise.
*/
YASM_LIB_DECL
int yasm_symrec_is_abs(const yasm_symrec *sym);
/** Determine if symbol is a special symbol.
* \param sym symbol
* \return 0 if symbol is not a special symbol, nonzero otherwise.
*/
YASM_LIB_DECL
int yasm_symrec_is_special(const yasm_symrec *sym);
/** Determine if symbol is a label representing the current assembly position.
* \param sym symbol
* \return 0 if symbol is not a current position label, nonzero otherwise.
*/
YASM_LIB_DECL
int yasm_symrec_is_curpos(const yasm_symrec *sym);
/** Set object-extended valparams.
* \param sym symbol
* \param objext_valparams object-extended valparams
*/
YASM_LIB_DECL
void yasm_symrec_set_objext_valparams
(yasm_symrec *sym, /*@only@*/ yasm_valparamhead *objext_valparams);
/** Get object-extended valparams, if any, associated with symbol's
* declaration.
* \param sym symbol
* \return Object-extended valparams (NULL if none).
*/
YASM_LIB_DECL
/*@null@*/ /*@dependent@*/ yasm_valparamhead *yasm_symrec_get_objext_valparams
(yasm_symrec *sym);
/** Set common size of symbol.
* \param sym symbol
* \param common_size common size expression
*/
YASM_LIB_DECL
void yasm_symrec_set_common_size
(yasm_symrec *sym, /*@only@*/ yasm_expr *common_size);
/** Get common size of symbol, if symbol is declared COMMON and a size was set
* for it.
* \param sym symbol
* \return Common size (NULL if none).
*/
YASM_LIB_DECL
/*@dependent@*/ /*@null@*/ yasm_expr **yasm_symrec_get_common_size
(yasm_symrec *sym);
/** Get associated data for a symbol and data callback.
* \param sym symbol
* \param callback callback used when adding data
* \return Associated data (NULL if none).
*/
YASM_LIB_DECL
/*@dependent@*/ /*@null@*/ void *yasm_symrec_get_data
(yasm_symrec *sym, const yasm_assoc_data_callback *callback);
/** Add associated data to a symbol.
* \attention Deletes any existing associated data for that data callback.
* \param sym symbol
* \param callback callback
* \param data data to associate
*/
YASM_LIB_DECL
void yasm_symrec_add_data(yasm_symrec *sym,
const yasm_assoc_data_callback *callback,
/*@only@*/ /*@null@*/ void *data);
/** Print a symbol. For debugging purposes.
* \param f file
* \param indent_level indentation level
* \param sym symbol
*/
YASM_LIB_DECL
void yasm_symrec_print(const yasm_symrec *sym, FILE *f, int indent_level);
#endif

View File

@ -0,0 +1,408 @@
/**
* \file libyasm/valparam.h
* \brief YASM value/parameter interface.
*
* \license
* Copyright (C) 2001-2007 Peter Johnson
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* - Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* - Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND OTHER CONTRIBUTORS ``AS IS''
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR OTHER CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
* \endlicense
*/
#ifndef YASM_VALPARAM_H
#define YASM_VALPARAM_H
#ifndef YASM_LIB_DECL
#define YASM_LIB_DECL
#endif
/** Value/parameter pair. \internal */
struct yasm_valparam {
/*@reldef@*/ STAILQ_ENTRY(yasm_valparam) link; /**< Next pair in list */
/*@owned@*/ /*@null@*/ char *val; /**< Value */
/** Parameter type. */
enum yasm_param_type {
YASM_PARAM_ID, /**< Identifier */
YASM_PARAM_STRING, /**< String */
YASM_PARAM_EXPR /**< Expression */
} type; /**< Parameter type */
/** Parameter value. */
union yasm_param {
/*@owned@*/ char *id; /**< Identifier */
/*@owned@*/ char *str; /**< String */
/*@owned@*/ yasm_expr *e; /**< Expression */
} param; /**< Parameter */
/** Prefix character that indicates a raw identifier. When
* yasm_vp_string() is called on a #YASM_PARAM_ID, all characters are
* returned. When yasm_vp_id() is called on a #YASM_PARAM_ID, if the
* identifier begins with this character, this character is stripped
* from the returned value.
*/
char id_prefix;
};
/** Linked list of value/parameter pairs. \internal */
/*@reldef@*/ STAILQ_HEAD(yasm_valparamhead, yasm_valparam);
/** Directive list entry structure. */
struct yasm_directive {
/** Directive name. GAS directives should include the ".", NASM
* directives should just be the raw name (not including the []).
* NULL entry required to terminate list of directives.
*/
/*@null@*/ const char *name;
const char *parser; /**< Parser keyword */
/** Handler callback function for the directive.
* \param object object
* \param valparams value/parameters
* \param objext_valparams object format-specific value/parameters
* \param line virtual line (from yasm_linemap)
*/
void (*handler) (yasm_object *object, yasm_valparamhead *valparams,
yasm_valparamhead *objext_valparams, unsigned long line);
/** Flags for pre-handler parameter checking. */
enum yasm_directive_flags {
YASM_DIR_ANY = 0, /**< Any valparams accepted */
YASM_DIR_ARG_REQUIRED = 1, /**< Require at least 1 valparam */
YASM_DIR_ID_REQUIRED = 2 /**< First valparam must be ID */
} flags;
};
/** Call a directive. Performs any valparam checks asked for by the
* directive prior to call. Note that for a variety of reasons, a directive
* can generate an error.
* \param directive directive
* \param object object
* \param valparams value/parameters
* \param objext_valparams object format-specific value/parameters
* \param line virtual line (from yasm_linemap)
*/
YASM_LIB_DECL
void yasm_call_directive(const yasm_directive *directive, yasm_object *object,
yasm_valparamhead *valparams,
yasm_valparamhead *objext_valparams,
unsigned long line);
/** Create a new valparam with identifier parameter.
* \param v value
* \param p parameter
* \param id_prefix identifier prefix for raw identifiers
* \return Newly allocated valparam.
*/
YASM_LIB_DECL
yasm_valparam *yasm_vp_create_id(/*@keep@*/ char *v, /*@keep@*/ char *p,
int id_prefix);
/** Create a new valparam with string parameter.
* \param v value
* \param p parameter
* \return Newly allocated valparam.
*/
YASM_LIB_DECL
yasm_valparam *yasm_vp_create_string(/*@keep@*/ char *v, /*@keep@*/ char *p);
/** Create a new valparam with expression parameter.
* \param v value
* \param p parameter
* \return Newly allocated valparam.
*/
YASM_LIB_DECL
yasm_valparam *yasm_vp_create_expr(/*@keep@*/ char *v,
/*@keep@*/ yasm_expr *p);
/** Get a valparam parameter as an expr. If the parameter is an identifier,
* it's treated as a symbol (yasm_symtab_use() is called to convert it).
* \param vp valparam
* \param symtab symbol table
* \param line virtual line
* \return Expression, or NULL if vp is NULL or the parameter cannot be
* converted to an expression.
*/
YASM_LIB_DECL
/*@null@*/ /*@only@*/ yasm_expr *yasm_vp_expr
(const yasm_valparam *vp, yasm_symtab *symtab, unsigned long line);
/** Get a valparam parameter as a string. If the parameter is an identifier,
* it's treated as a string.
* \param vp valparam
* \return String, or NULL if vp is NULL or the parameter cannot be realized
* as a string.
*/
YASM_LIB_DECL
/*@null@*/ /*@dependent@*/ const char *yasm_vp_string(const yasm_valparam *vp);
/** Get a valparam parameter as an identifier.
* \param vp valparam
* \return Identifier (string), or NULL if vp is NULL or the parameter is not
* an identifier.
*/
YASM_LIB_DECL
/*@null@*/ /*@dependent@*/ const char *yasm_vp_id(const yasm_valparam *vp);
/** Create a new linked list of valparams.
* \return Newly allocated valparam list.
*/
YASM_LIB_DECL
yasm_valparamhead *yasm_vps_create(void);
/** Destroy a list of valparams (created with yasm_vps_create).
* \param headp list of valparams
*/
YASM_LIB_DECL
void yasm_vps_destroy(yasm_valparamhead *headp);
/** Initialize linked list of valparams.
* \param headp linked list
*/
void yasm_vps_initialize(/*@out@*/ yasm_valparamhead *headp);
#ifndef YASM_DOXYGEN
#define yasm_vps_initialize(headp) STAILQ_INIT(headp)
#endif
/** Destroy (free allocated memory for) linked list of valparams (created with
* yasm_vps_initialize).
* \warning Deletes val/params.
* \param headp linked list
*/
YASM_LIB_DECL
void yasm_vps_delete(yasm_valparamhead *headp);
/** Append valparam to tail of linked list.
* \param headp linked list
* \param vp valparam
*/
void yasm_vps_append(yasm_valparamhead *headp, /*@keep@*/ yasm_valparam *vp);
#ifndef YASM_DOXYGEN
#define yasm_vps_append(headp, vp) do { \
if (vp) \
STAILQ_INSERT_TAIL(headp, vp, link); \
} while(0)
#endif
/** Get first valparam in linked list.
* \param headp linked list
* \return First valparam in linked list.
*/
/*@null@*/ /*@dependent@*/ yasm_valparam *yasm_vps_first
(yasm_valparamhead *headp);
#ifndef YASM_DOXYGEN
#define yasm_vps_first(headp) STAILQ_FIRST(headp)
#endif
/** Get next valparam in linked list.
* \param cur previous valparam in linked list
* \return Next valparam in linked list.
*/
/*@null@*/ /*@dependent@*/ yasm_valparam *yasm_vps_next(yasm_valparam *cur);
#ifndef YASM_DOXYGEN
#define yasm_vps_next(cur) STAILQ_NEXT(cur, link)
#endif
/** Iterate through linked list of valparams.
* \internal
* \param iter iterator variable
* \param headp linked list
*/
#ifndef YASM_DOXYGEN
#define yasm_vps_foreach(iter, headp) STAILQ_FOREACH(iter, headp, link)
#endif
/** Print linked list of valparams. For debugging purposes.
* \param f file
* \param headp linked list
*/
YASM_LIB_DECL
void yasm_vps_print(/*@null@*/ const yasm_valparamhead *headp, FILE *f);
/** Directive valparam parse helper structure. */
typedef struct yasm_dir_help {
/** Value portion of val=param (if needsparam=1), or standalone identifier
* (if needsparam=0).
*/
const char *name;
/** 1 if value requires parameter, 0 if it must not have a parameter. */
int needsparam;
/** Helper callback function if name and parameter existence match.
* \param obj obj passed into yasm_dir_helper()
* \param vp value/parameter
* \param line line passed into yasm_dir_helper()
* \param data data passed into yasm_dir_helper() plus
#yasm_dir_help.off offset
* \param arg #yasm_dir_help.arg argument
* \return -1 on error, 0 otherwise.
*/
int (*helper) (void *obj, yasm_valparam *vp, unsigned long line,
void *data, uintptr_t arg);
/** Offset added to data pointer passed into yasm_dir_helper() before
* data pointer is given to #yasm_dir_help.helper(). This is so that
* a structure can be passed into yasm_dir_helper() and this can be an
* offsetof() to point the helper function to a specific structure
* member.
*/
size_t off;
/** Argument to pass in as the arg parameter to #yasm_dir_help.helper().
*/
uintptr_t arg;
} yasm_dir_help;
/** Help parse a list of directive value/parameters. Takes an array of
* #yasm_dir_help structures and tries to match val=param (or just val)
* against the passed value/parameters. When no match is found in the
* array of help structures, calls helper_valparam.
* \param obj object to be passed to yasm_dir_help.helper() or
* helper_valparam() callback
* \param vp_first first value/parameter to examine
* \param line virtual line number; passed down to helper callback
* \param help array of #yasm_dir_help structures
* \param nhelp number of array elements
* \param data base data pointer; if a match is found,
* the respective #yasm_dir_help.off is added to this
* prior to it being passed to the helper callback
* \param helper_valparam catch-all callback; should return -1 on error,
* 0 if not matched, 1 if matched.
* \return -1 on error, 1 if any arguments matched (including via
* catch-all callback), 0 if no match.
*/
YASM_LIB_DECL
int yasm_dir_helper(void *obj, yasm_valparam *vp_first, unsigned long line,
const yasm_dir_help *help, size_t nhelp, void *data,
int (*helper_valparam) (void *object,
yasm_valparam *vp,
unsigned long line,
void *data));
/** Standard helper for yasm_dir_helper() that simply sets a flag when called.
* It does not look at the vp; rather, it uses the value of the arg parameter,
* and stores an unsigned long value to data.
* \param obj unused
* \param vp unused
* \param line unused
* \param data pointer to an unsigned long
* \param arg flag to set
* \return 0
*/
YASM_LIB_DECL
int yasm_dir_helper_flag_set(void *obj, yasm_valparam *vp, unsigned long line,
void *data, uintptr_t arg);
/** Standard helper for yasm_dir_helper() that simply ORs a flag when called.
* It does not look at the vp; rather, it uses the value of the arg parameter,
* and ORs it with the unsigned long value in data.
* \param obj unused
* \param vp unused
* \param line unused
* \param data pointer to an unsigned long
* \param arg flag to OR
* \return 0
*/
YASM_LIB_DECL
int yasm_dir_helper_flag_or(void *obj, yasm_valparam *vp, unsigned long line,
void *data, uintptr_t arg);
/** Standard helper for yasm_dir_helper() that simply ANDs a flag when called.
* It does not look at the vp; rather, it uses the value of the arg parameter,
* and ANDs its inverse (~) with the unsigned long value in data.
* \param obj unused
* \param vp unused
* \param line unused
* \param data pointer to an unsigned long
* \param arg flag to AND
* \return 0
*/
YASM_LIB_DECL
int yasm_dir_helper_flag_and(void *obj, yasm_valparam *vp, unsigned long line,
void *data, uintptr_t arg);
/** Standard helper for yasm_dir_helper() that parses an expr parameter.
* The #yasm_dir_help structure that uses this function should have
* needsparam=1. The obj parameter to yasm_dir_helper() when this helper
* is used MUST point to a #yasm_object. In addition, the data parameter
* that is ultimately passed to this function (e.g. yasm_dir_helper() data
* parameter plus #yasm_dir_help.off) must point to a #yasm_expr *
* initialized to NULL.
* \param obj object; must be #yasm_object
* \param vp valparam
* \param line virtual line number
* \param data pointer to #yasm_expr *
* \param arg unused argument
* \return -1 on error, 0 otherwise.
*/
YASM_LIB_DECL
int yasm_dir_helper_expr(void *obj, yasm_valparam *vp, unsigned long line,
void *data, uintptr_t arg);
/** Standard helper for yasm_dir_helper() that parses an intnum parameter.
* The #yasm_dir_help structure that uses this function should have
* needsparam=1. The obj parameter to yasm_dir_helper() when this helper
* is used MUST point to a #yasm_object. In addition, the data parameter
* that is ultimately passed to this function (e.g. yasm_dir_helper() data
* parameter plus #yasm_dir_help.off) must point to a #yasm_intnum *
* initialized to NULL.
* \param obj object; must be #yasm_object
* \param vp valparam
* \param line virtual line number
* \param data pointer to #yasm_intnum *
* \param arg unused argument
* \return -1 on error, 0 otherwise.
*/
YASM_LIB_DECL
int yasm_dir_helper_intn(void *obj, yasm_valparam *vp, unsigned long line,
void *data, uintptr_t arg);
/** Standard helper for yasm_dir_helper() that parses an string (or
* standalone identifier) parameter.
* The #yasm_dir_help structure that uses this function should have
* needsparam=1. The data parameter that is ultimately passed to this
* function (e.g. yasm_dir_helper() data parameter plus #yasm_dir_help.off)
* must point to a char * initialized to NULL.
* \param obj unused
* \param vp valparam
* \param line unused
* \param data pointer to char *
* \param arg unused
* \return -1 on error, 0 otherwise.
*/
YASM_LIB_DECL
int yasm_dir_helper_string(void *obj, yasm_valparam *vp, unsigned long line,
void *data, uintptr_t arg);
/** Standard catch-all callback fro yasm_dir_helper(). Generates standard
* warning for all valparams.
* \param obj unused
* \param vp valparam
* \param line unused
* \param data unused
* \return 0
*/
YASM_LIB_DECL
int yasm_dir_helper_valparam_warn(void *obj, yasm_valparam *vp,
unsigned long line, void *data);
#endif

View File

@ -0,0 +1,172 @@
/**
* \file libyasm/value.h
* \brief YASM value interface.
*
* \license
* Copyright (C) 2006-2007 Peter Johnson
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* - Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* - Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND OTHER CONTRIBUTORS ``AS IS''
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR OTHER CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
* \endlicense
*/
#ifndef YASM_VALUE_H
#define YASM_VALUE_H
#ifndef YASM_LIB_DECL
#define YASM_LIB_DECL
#endif
/** Initialize a #yasm_value with just an expression. No processing is
* performed, the expression is simply stuck into value.abs and the other
* fields are initialized. Use yasm_expr_extract_value() to perform "smart"
* processing into a #yasm_value. This function is intended for use during
* parsing simply to ensure all fields of the value are initialized; after
* the parse is complete, yasm_value_extract() should be called to finalize
* the value. The value defaults to unsigned.
* \param value value to be initialized
* \param e expression (kept)
* \param size value size (in bits)
*/
YASM_LIB_DECL
void yasm_value_initialize(/*@out@*/ yasm_value *value,
/*@null@*/ /*@kept@*/ yasm_expr *e,
unsigned int size);
/** Initialize a #yasm_value with just a symrec. No processing is performed,
* the symrec is simply stuck into value.rel and the other fields are
* initialized.
* \param value value to be initialized
* \param sym symrec
* \param size value size (in bits)
*/
YASM_LIB_DECL
void yasm_value_init_sym(/*@out@*/ yasm_value *value,
/*@null@*/ yasm_symrec *sym, unsigned int size);
/** Initialize a #yasm_value as a copy of another yasm_value. Any expressions
* within orig are copied, so it's safe to delete the copy.
* \param value value (copy to create)
* \param orig original value
*/
YASM_LIB_DECL
void yasm_value_init_copy(yasm_value *value, const yasm_value *orig);
/** Frees any memory inside value; does not free value itself.
* \param value value
*/
YASM_LIB_DECL
void yasm_value_delete(yasm_value *value);
/** Set a value to be relative to the current assembly position rather than
* relative to the section start.
* \param value value
* \param bc bytecode containing value
* \param ip_rel if nonzero, indicates IP-relative data relocation,
* sometimes used to generate special relocations
* \note If value is just an absolute value, will get an absolute symrec to
* reference to (via bc's symbol table).
*/
YASM_LIB_DECL
void yasm_value_set_curpos_rel(yasm_value *value, yasm_bytecode *bc,
unsigned int ip_rel);
/** Perform yasm_value_finalize_expr() on a value that already exists from
* being initialized with yasm_value_initialize().
* \param value value
* \param precbc previous bytecode to bytecode containing value
* \return Nonzero if value could not be split.
*/
YASM_LIB_DECL
int yasm_value_finalize(yasm_value *value, /*@null@*/ yasm_bytecode *precbc);
/** Break a #yasm_expr into a #yasm_value constituent parts. Extracts
* the relative portion of the value, SEG and WRT portions, and top-level
* right shift, if any. Places the remaining expr into the absolute
* portion of the value. Essentially a combination of yasm_value_initialize()
* and yasm_value_finalize(). First expands references to symrecs in
* absolute sections by expanding with the absolute section start plus the
* symrec offset within the absolute section.
* \param value value to store split portions into
* \param e expression input
* \param precbc previous bytecode to bytecode containing expression
* \param size value size (in bits)
* \return Nonzero if the expr could not be split into a value for some
* reason (e.g. the relative portion was not added, but multiplied,
* etc).
* \warning Do not use e after this call. Even if an error is returned, e
* is stored into value.
* \note This should only be called after the parse is complete. Calling
* before the parse is complete will usually result in an error return.
*/
YASM_LIB_DECL
int yasm_value_finalize_expr(/*@out@*/ yasm_value *value,
/*@null@*/ /*@kept@*/ yasm_expr *e,
/*@null@*/ yasm_bytecode *precbc,
unsigned int size);
/** Get value if absolute or PC-relative section-local relative. Returns NULL
* otherwise.
* \param value value
* \param bc current bytecode (for PC-relative calculation); if
* NULL, NULL is returned for PC-relative values.
* \param calc_bc_dist if nonzero, calculates bytecode distances in absolute
* portion of value
* \note Adds in value.rel (correctly) if PC-relative and in the same section
* as bc (and there is no WRT or SEG).
* \return Intnum if can be resolved to integer value, otherwise NULL.
*/
YASM_LIB_DECL
/*@null@*/ /*@only@*/ yasm_intnum *yasm_value_get_intnum
(yasm_value *value, /*@null@*/ yasm_bytecode *bc, int calc_bc_dist);
/** Output value if constant or PC-relative section-local. This should be
* used from objfmt yasm_output_value_func() functions.
* functions.
* \param value value
* \param buf buffer for byte representation
* \param destsize destination size (in bytes)
* \param bc current bytecode (usually passed into higher-level
* calling function)
* \param warn enables standard warnings: zero for none;
* nonzero for overflow/underflow floating point and
* integer warnings
* \param arch architecture
* \note Adds in value.rel (correctly) if PC-relative and in the same section
* as bc (and there is no WRT or SEG); if this is not the desired
* behavior, e.g. a reloc is needed in this case, don't use this
* function!
* \return 0 if no value output due to value needing relocation;
* 1 if value output; -1 if error.
*/
YASM_LIB_DECL
int yasm_value_output_basic
(yasm_value *value, /*@out@*/ unsigned char *buf, size_t destsize,
yasm_bytecode *bc, int warn, yasm_arch *arch);
/** Print a value. For debugging purposes.
* \param value value
* \param indent_level indentation level
* \param f file
*/
YASM_LIB_DECL
void yasm_value_print(const yasm_value *value, FILE *f, int indent_level);
#endif

View File

@ -0,0 +1,349 @@
'\" t
.\" Title: yasm
.\" Author: Peter Johnson <peter@tortall.net>
.\" Generator: DocBook XSL Stylesheets v1.75.2 <http://docbook.sf.net/>
.\" Date: April 2007
.\" Manual: The Yasm Modular Assembler
.\" Source: Yasm
.\" Language: English
.\"
.TH "YASM" "1" "April 2007" "Yasm" "The Yasm Modular Assembler"
.\" -----------------------------------------------------------------
.\" * set default formatting
.\" -----------------------------------------------------------------
.\" disable hyphenation
.nh
.\" disable justification (adjust text to left margin only)
.ad l
.\" -----------------------------------------------------------------
.\" * MAIN CONTENT STARTS HERE *
.\" -----------------------------------------------------------------
.SH "NAME"
yasm \- The Yasm Modular Assembler
.SH "SYNOPSIS"
.HP \w'\fByasm\fR\ 'u
\fByasm\fR [\fB\-f\ \fR\fB\fIformat\fR\fR] [\fB\-o\ \fR\fB\fIoutfile\fR\fR] [\fB\fIother\ options\fR\fR...] {\fIinfile\fR}
.HP \w'\fByasm\fR\ 'u
\fByasm\fR \fB\-h\fR
.SH "DESCRIPTION"
.PP
The Yasm Modular Assembler is a portable, retargetable assembler written under the
\(lqnew\(rq
(2 or 3 clause) BSD license\&. Yasm currently supports the x86 and AMD64 instruction sets, accepts NASM and GAS assembler syntaxes, outputs binary, ELF32, ELF64, COFF, Win32, and Win64 object formats, and generates source debugging information in STABS, DWARF 2, and CodeView 8 formats\&.
.PP
YASM consists of the
\fByasm\fR
command, libyasm, the core backend library, and a large number of modules\&. Currently, libyasm and the loadable modules are statically built into the
\fByasm\fR
executable\&.
.PP
The
\fByasm\fR
command assembles the file infile and directs output to the file
\fIoutfile\fR
if specified\&. If
\fIoutfile\fR
is not specified,
\fByasm\fR
will derive a default output file name from the name of its input file, usually by appending
\&.o
or
\&.obj, or by removing all extensions for a raw binary file\&. Failing that, the output file name will be
yasm\&.out\&.
.PP
If called with an
\fIinfile\fR
of
\(lq\-\(rq,
\fByasm\fR
assembles the standard input and directs output to the file
\fIoutfile\fR, or
yasm\&.out
if no
\fIoutfile\fR
is specified\&.
.SH "OPTIONS"
.PP
Many options may be given in one of two forms: either a dash followed by a single letter, or two dashes followed by a long option name\&. Options are listed in alphabetical order\&.
.SS "General Options"
.PP
\fB\-a \fR\fB\fIarch\fR\fR or \fB\-\-arch=\fR\fB\fIarch\fR\fR: Select target architecture
.RS 4
Selects the target architecture\&. The default architecture is
\(lqx86\(rq, which supports both the IA\-32 and derivatives and AMD64 instruction sets\&. To print a list of available architectures to standard output, use
\(lqhelp\(rq
as
\fIarch\fR\&. See
\fByasm_arch\fR(7)
for a list of supported architectures\&.
.RE
.PP
\fB\-f \fR\fB\fIformat\fR\fR or \fB\-\-oformat=\fR\fB\fIformat\fR\fR: Select object format
.RS 4
Selects the output object format\&. The default object format is
\(lqbin\(rq, which is a flat format binary with no relocation\&. To print a list of available object formats to standard output, use
\(lqhelp\(rq
as
\fIformat\fR\&. See
\fByasm_objfmts\fR(7)
for a list of supported object formats\&.
.RE
.PP
\fB\-g \fR\fB\fIdebug\fR\fR or \fB\-\-dformat=\fR\fB\fIdebug\fR\fR: Select debugging format
.RS 4
Selects the debugging format for debug information\&. Debugging information can be used by a debugger to associate executable code back to the source file or get data structure and type information\&. Available debug formats vary between different object formats;
\fByasm\fR
will error when an invalid combination is selected\&. The default object format is selected by the object format\&. To print a list of available debugging formats to standard output, use
\(lqhelp\(rq
as
\fIdebug\fR\&. See
\fByasm_dbgfmts\fR(7)
for a list of supported debugging formats\&.
.RE
.PP
\fB\-L \fR\fB\fIlist\fR\fR or \fB\-\-lformat=\fR\fB\fIlist\fR\fR: Select list file format
.RS 4
Selects the format/style of the output list file\&. List files typically intermix the original source with the machine code generated by the assembler\&. The default list format is
\(lqnasm\(rq, which mimics the NASM list file format\&. To print a list of available list file formats to standard output, use
\(lqhelp\(rq
as
\fIlist\fR\&.
.RE
.PP
\fB\-l \fR\fB\fIlistfile\fR\fR or \fB\-\-list=\fR\fB\fIlistfile\fR\fR: Specify list filename
.RS 4
Specifies the name of the output list file\&. If this option is not used, no list file is generated\&.
.RE
.PP
\fB\-m \fR\fB\fImachine\fR\fR or \fB\-\-machine=\fR\fB\fImachine\fR\fR: Select target machine architecture
.RS 4
Selects the target machine architecture\&. Essentially a subtype of the selected architecture, the machine type selects between major subsets of an architecture\&. For example, for the
\(lqx86\(rq
architecture, the two available machines are
\(lqx86\(rq, which is used for the IA\-32 and derivative 32\-bit instruction set, and
\(lqamd64\(rq, which is used for the 64\-bit instruction set\&. This differentiation is required to generate the proper object file for relocatable object formats such as COFF and ELF\&. To print a list of available machines for a given architecture to standard output, use
\(lqhelp\(rq
as
\fImachine\fR
and the given architecture using
\fB\-a \fR\fB\fIarch\fR\fR\&. See
\fByasm_arch\fR(7)
for more details\&.
.RE
.PP
\fB\-o \fR\fB\fIfilename\fR\fR or \fB\-\-objfile=\fR\fB\fIfilename\fR\fR: Specify object filename
.RS 4
Specifies the name of the output file, overriding any default name generated by Yasm\&.
.RE
.PP
\fB\-p \fR\fB\fIparser\fR\fR or \fB\-\-parser=\fR\fB\fIparser\fR\fR: Select parser
.RS 4
Selects the parser (the assembler syntax)\&. The default parser is
\(lqnasm\(rq, which emulates the syntax of NASM, the Netwide Assembler\&. Another available parser is
\(lqgas\(rq, which emulates the syntax of GNU AS\&. To print a list of available parsers to standard output, use
\(lqhelp\(rq
as
\fIparser\fR\&. See
\fByasm_parsers\fR(7)
for a list of supported parsers\&.
.RE
.PP
\fB\-r \fR\fB\fIpreproc\fR\fR or \fB\-\-preproc=\fR\fB\fIpreproc\fR\fR: Select preprocessor
.RS 4
Selects the preprocessor to use on the input file before passing it to the parser\&. Preprocessors often provide macro functionality that is not included in the main parser\&. The default preprocessor is
\(lqnasm\(rq, which is an imported version of the actual NASM preprocessor\&. A
\(lqraw\(rq
preprocessor is also available, which simply skips the preprocessing step, passing the input file directly to the parser\&. To print a list of available preprocessors to standard output, use
\(lqhelp\(rq
as
\fIpreproc\fR\&.
.RE
.PP
\fB\-h\fR or \fB\-\-help\fR: Print a summary of options
.RS 4
Prints a summary of invocation options\&. All other options are ignored, and no output file is generated\&.
.RE
.PP
\fB\-\-version\fR: Get the Yasm version
.RS 4
This option causes Yasm to prints the version number of Yasm as well as a license summary to standard output\&. All other options are ignored, and no output file is generated\&.
.RE
.SS "Warning Options"
.PP
\fB\-W\fR
options have two contrary forms:
\fB\-W\fR\fB\fIname\fR\fR
and
\fB\-Wno\-\fR\fB\fIname\fR\fR\&. Only the non\-default forms are shown here\&.
.PP
The warning options are handled in the order given on the command line, so if
\fB\-w\fR
is followed by
\fB\-Worphan\-labels\fR, all warnings are turned off
\fIexcept\fR
for orphan\-labels\&.
.PP
\fB\-w\fR: Inhibit all warning messages
.RS 4
This option causes Yasm to inhibit all warning messages\&. As discussed above, this option may be followed by other options to re\-enable specified warnings\&.
.RE
.PP
\fB\-Werror\fR: Treat warnings as errors
.RS 4
This option causes Yasm to treat all warnings as errors\&. Normally warnings do not prevent an object file from being generated and do not result in a failure exit status from
\fByasm\fR, whereas errors do\&. This option makes warnings equivalent to errors in terms of this behavior\&.
.RE
.PP
\fB\-Wno\-unrecognized\-char\fR: Do not warn on unrecognized input characters
.RS 4
Causes Yasm to not warn on unrecognized characters found in the input\&. Normally Yasm will generate a warning for any non\-ASCII character found in the input file\&.
.RE
.PP
\fB\-Worphan\-labels\fR: Warn on labels lacking a trailing option
.RS 4
When using the NASM\-compatible parser, causes Yasm to warn about labels found alone on a line without a trailing colon\&. While these are legal labels in NASM syntax, they may be unintentional, due to typos or macro definition ordering\&.
.RE
.PP
\fB\-X \fR\fB\fIstyle\fR\fR: Change error/warning reporting style
.RS 4
Selects a specific output style for error and warning messages\&. The default is
\(lqgnu\(rq
style, which mimics the output of
\fBgcc\fR\&. The
\(lqvc\(rq
style is also available, which mimics the output of Microsoft\'s Visual C++ compiler\&.
.sp
This option is available so that Yasm integrates more naturally into IDE environments such as
Visual Studio
or
Emacs, allowing the IDE to correctly recognize the error/warning message as such and link back to the offending line of source code\&.
.RE
.SS "Preprocessor Options"
.PP
While these preprocessor options theoretically will affect any preprocessor, the only preprocessor currently in Yasm is the
\(lqnasm\(rq
preprocessor\&.
.PP
\fB\-D \fR\fB\fImacro[=value]\fR\fR: Pre\-define a macro
.RS 4
Pre\-defines a single\-line macro\&. The value is optional (if no value is given, the macro is still defined, but to an empty value)\&.
.RE
.PP
\fB\-e\fR or \fB\-\-preproc\-only\fR: Only preprocess
.RS 4
Stops assembly after the preprocessing stage; preprocessed output is sent to the specified output name or, if no output name is specified, the standard output\&. No object file is produced\&.
.RE
.PP
\fB\-I \fR\fB\fIpath\fR\fR: Add include file path
.RS 4
Adds directory
\fIpath\fR
to the search path for include files\&. The search path defaults to only including the directory in which the source file resides\&.
.RE
.PP
\fB\-P \fR\fB\fIfilename\fR\fR: Pre\-include a file
.RS 4
Pre\-includes file
\fIfilename\fR, making it look as though
\fIfilename\fR
was prepended to the input\&. Can be useful for prepending multi\-line macros that the
\fB\-D\fR
can\'t support\&.
.RE
.PP
\fB\-U \fR\fB\fImacro\fR\fR: Undefine a macro
.RS 4
Undefines a single\-line macro (may be either a built\-in macro or one defined earlier in the command line with
\fB\-D\fR\&.
.RE
.SH "EXAMPLES"
.PP
To assemble NASM syntax, 32\-bit x86 source
source\&.asm
into ELF file
source\&.o, warning on orphan labels:
.sp
.if n \{\
.RS 4
.\}
.nf
yasm \-f elf32 \-Worphan\-labels source\&.asm
.fi
.if n \{\
.RE
.\}
.PP
To assemble NASM syntax AMD64 source
x\&.asm
into Win64 file
object\&.obj:
.sp
.if n \{\
.RS 4
.\}
.nf
yasm \-f win64 \-o object\&.obj x\&.asm
.fi
.if n \{\
.RE
.\}
.PP
To assemble already preprocessed NASM syntax x86 source
y\&.asm
into flat binary file
y\&.com:
.sp
.if n \{\
.RS 4
.\}
.nf
yasm \-f bin \-r raw \-o y\&.com y\&.asm
.fi
.if n \{\
.RE
.\}
.SH "DIAGNOSTICS"
.PP
The
\fByasm\fR
command exits 0 on success, and nonzero if an error occurs\&.
.SH "COMPATIBILITY"
.PP
Yasm\'s NASM parser and preprocessor, while they strive to be as compatible as possible with NASM, have a few incompatibilities due to YASM\'s different internal structure\&.
.PP
Yasm\'s GAS parser and preprocessor are missing a number of features present in GNU AS\&.
.SH "RESTRICTIONS"
.PP
As object files are often architecture and machine dependent, not all combinations of object formats, architectures, and machines are legal; trying to use an invalid combination will result in an error\&.
.PP
There is no support for symbol maps\&.
.SH "SEE ALSO"
.PP
\fByasm_arch\fR(7),
\fByasm_dbgfmts\fR(7),
\fByasm_objfmts\fR(7),
\fByasm_parsers\fR(7)
.PP
Related tools:
\fBas\fR(1),
\fBld\fR(1),
\fBnasm\fR(1)
.SH "BUGS"
.PP
When using the
\(lqx86\(rq
architecture, it is overly easy to generate AMD64 code (using the
\fBBITS 64\fR
directive) and generate a 32\-bit object file (by failing to specify
\fB\-m amd64\fR
or selecting a 64\-bit object format such as ELF64 on the command line)\&.
.SH "AUTHOR"
.PP
\fBPeter Johnson\fR <\&peter@tortall\&.net\&>
.RS 4
Author.
.RE
.SH "COPYRIGHT"
.br
Copyright \(co 2004, 2005, 2006, 2007 Peter Johnson
.br

View File

@ -0,0 +1,860 @@
'\" t
.\" Title: yasm_arch
.\" Author: Peter Johnson <peter@tortall.net>
.\" Generator: DocBook XSL Stylesheets v1.76.1 <http://docbook.sf.net/>
.\" Date: October 2006
.\" Manual: Yasm Supported Architectures
.\" Source: Yasm
.\" Language: English
.\"
.TH "YASM_ARCH" "7" "October 2006" "Yasm" "Yasm Supported Architectures"
.\" -----------------------------------------------------------------
.\" * Define some portability stuff
.\" -----------------------------------------------------------------
.\" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.\" http://bugs.debian.org/507673
.\" http://lists.gnu.org/archive/html/groff/2009-02/msg00013.html
.\" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.ie \n(.g .ds Aq \(aq
.el .ds Aq '
.\" -----------------------------------------------------------------
.\" * set default formatting
.\" -----------------------------------------------------------------
.\" disable hyphenation
.nh
.\" disable justification (adjust text to left margin only)
.ad l
.\" -----------------------------------------------------------------
.\" * MAIN CONTENT STARTS HERE *
.\" -----------------------------------------------------------------
.SH "NAME"
yasm_arch \- Yasm Supported Target Architectures
.SH "SYNOPSIS"
.HP \w'\fByasm\fR\ 'u
\fByasm\fR \fB\-a\ \fR\fB\fIarch\fR\fR [\fB\-m\ \fR\fB\fImachine\fR\fR] \fB\fI\&.\&.\&.\fR\fR
.SH "DESCRIPTION"
.PP
The standard Yasm distribution includes a number of modules for different target architectures\&. Each target architecture can support one or more machine architectures\&.
.PP
The architecture and machine are selected on the
\fByasm\fR(1)
command line by use of the
\fB\-a \fR\fB\fIarch\fR\fR
and
\fB\-m \fR\fB\fImachine\fR\fR
command line options, respectively\&.
.PP
The machine architecture may also automatically be selected by certain object formats\&. For example, the
\(lqelf32\(rq
object format selects the
\(lqx86\(rq
machine architecture by default, while the
\(lqelf64\(rq
object format selects the
\(lqamd64\(rq
machine architecture by default\&.
.SH "X86 ARCHITECTURE"
.PP
The
\(lqx86\(rq
architecture supports the IA\-32 instruction set and derivatives and the AMD64 instruction set\&. It consists of two machines:
\(lqx86\(rq
(for the IA\-32 and derivatives) and
\(lqamd64\(rq
(for the AMD64 and derivatives)\&. The default machine for the
\(lqx86\(rq
architecture is the
\(lqx86\(rq
machine\&.
.SS "BITS Setting"
.PP
The x86 architecture BITS setting specifies to Yasm the processor mode in which the generated code is intended to execute\&. x86 processors can run in three different major execution modes: 16\-bit, 32\-bit, and on AMD64\-supporting processors, 64\-bit\&. As the x86 instruction set contains portions whose function is execution\-mode dependent (such as operand\-size and address\-size override prefixes), Yasm cannot assemble x86 instructions correctly unless it is told by the user in what processor mode the code will execute\&.
.PP
The BITS setting can be changed in a variety of ways\&. When using the NASM\-compatible parser, the BITS setting can be changed directly via the use of the
\fBBITS xx\fR
assembler directive\&. The default BITS setting is determined by the object format in use\&.
.SS "BITS 64 Extensions"
.PP
The AMD64 architecture is a new 64\-bit architecture developed by AMD, based on the 32\-bit x86 architecture\&. It extends the original x86 architecture by doubling the number of general purpose and SIMD registers, extending the arithmetic operations and address space to 64 bits, as well as other features\&.
.PP
Recently, Intel has introduced an essentially identical version of AMD64 called EM64T\&.
.PP
When an AMD64\-supporting processor is executing in 64\-bit mode, a number of additional extensions are available, including extra general purpose registers, extra SSE2 registers, and RIP\-relative addressing\&.
.PP
Yasm extends the base NASM syntax to support AMD64 as follows\&. To enable assembly of instructions for the 64\-bit mode of AMD64 processors, use the directive
\fBBITS 64\fR\&. As with NASM\*(Aqs BITS directive, this does not change the format of the output object file to 64 bits; it only changes the assembler mode to assume that the instructions being assembled will be run in 64\-bit mode\&. To specify an AMD64 object file, use
\fB\-m amd64\fR
on the Yasm command line, or explicitly target a 64\-bit object format such as
\fB\-f win64\fR
or
\fB\-f elf64\fR\&.
\fB\-f elfx32\fR
can be used to select 32\-bit ELF object format for AMD64 processors\&.
.sp
.it 1 an-trap
.nr an-no-space-flag 1
.nr an-break-flag 1
.br
.ps +1
\fBRegister Changes\fR
.RS 4
.PP
The additional 64\-bit general purpose registers are named r8\-r15\&. There are also 8\-bit (rXb), 16\-bit (rXw), and 32\-bit (rXd) subregisters that map to the least significant 8, 16, or 32 bits of the 64\-bit register\&. The original 8 general purpose registers have also been extended to 64\-bits: eax, edx, ecx, ebx, esi, edi, esp, and ebp have new 64\-bit versions called rax, rdx, rcx, rbx, rsi, rdi, rsp, and rbp respectively\&. The old 32\-bit registers map to the least significant bits of the new 64\-bit registers\&.
.PP
New 8\-bit registers are also available that map to the 8 least significant bits of rsi, rdi, rsp, and rbp\&. These are called sil, dil, spl, and bpl respectively\&. Unfortunately, due to the way instructions are encoded, these new 8\-bit registers are encoded the same as the old 8\-bit registers ah, dh, ch, and bh\&. The processor tells which is being used by the presence of the new REX prefix that is used to specify the other extended registers\&. This means it is illegal to mix the use of ah, dh, ch, and bh with an instruction that requires the REX prefix for other reasons\&. For instance:
.sp
.if n \{\
.RS 4
.\}
.nf
add ah, [r10]
.fi
.if n \{\
.RE
.\}
.PP
(NASM syntax) is not a legal instruction because the use of r10 requires a REX prefix, making it impossible to use ah\&.
.PP
In 64\-bit mode, an additional 8 SSE2 registers are also available\&. These are named xmm8\-xmm15\&.
.RE
.sp
.it 1 an-trap
.nr an-no-space-flag 1
.nr an-break-flag 1
.br
.ps +1
\fB64 Bit Instructions\fR
.RS 4
.PP
By default, most operations in 64\-bit mode remain 32\-bit; operations that are 64\-bit usually require a REX prefix (one bit in the REX prefix determines whether an operation is 64\-bit or 32\-bit)\&. Thus, essentially all 32\-bit instructions have a 64\-bit version, and the 64\-bit versions of instructions can use extended registers
\(lqfor free\(rq
(as the REX prefix is already present)\&. Examples in NASM syntax:
.sp
.if n \{\
.RS 4
.\}
.nf
mov eax, 1 ; 32\-bit instruction
.fi
.if n \{\
.RE
.\}
.sp
.if n \{\
.RS 4
.\}
.nf
mov rcx, 1 ; 64\-bit instruction
.fi
.if n \{\
.RE
.\}
.PP
Instructions that modify the stack (push, pop, call, ret, enter, and leave) are implicitly 64\-bit\&. Their 32\-bit counterparts are not available, but their 16\-bit counterparts are\&. Examples in NASM syntax:
.sp
.if n \{\
.RS 4
.\}
.nf
push eax ; illegal instruction
.fi
.if n \{\
.RE
.\}
.sp
.if n \{\
.RS 4
.\}
.nf
push rbx ; 1\-byte instruction
.fi
.if n \{\
.RE
.\}
.sp
.if n \{\
.RS 4
.\}
.nf
push r11 ; 2\-byte instruction with REX prefix
.fi
.if n \{\
.RE
.\}
.RE
.sp
.it 1 an-trap
.nr an-no-space-flag 1
.nr an-break-flag 1
.br
.ps +1
\fBImplicit Zero Extension\fR
.RS 4
.PP
Results of 32\-bit operations are implicitly zero\-extended to the upper 32 bits of the corresponding 64\-bit register\&. 16 and 8 bit operations, on the other hand, do not affect upper bits of the register (just as in 32\-bit and 16\-bit modes)\&. This can be used to generate smaller code in some instances\&. Examples in NASM syntax:
.sp
.if n \{\
.RS 4
.\}
.nf
mov ecx, 1 ; 1 byte shorter than mov rcx, 1
.fi
.if n \{\
.RE
.\}
.sp
.if n \{\
.RS 4
.\}
.nf
and edx, 3 ; equivalent to and rdx, 3
.fi
.if n \{\
.RE
.\}
.RE
.sp
.it 1 an-trap
.nr an-no-space-flag 1
.nr an-break-flag 1
.br
.ps +1
\fBImmediates\fR
.RS 4
.PP
For most instructions in 64\-bit mode, immediate values remain 32 bits; their value is sign\-extended into the upper 32 bits of the target register prior to being used\&. The exception is the mov instruction, which can take a 64\-bit immediate when the destination is a 64\-bit register\&. Examples in NASM syntax:
.sp
.if n \{\
.RS 4
.\}
.nf
add rax, 1 ; optimized down to signed 8\-bit
.fi
.if n \{\
.RE
.\}
.sp
.if n \{\
.RS 4
.\}
.nf
add rax, dword 1 ; force size to 32\-bit
.fi
.if n \{\
.RE
.\}
.sp
.if n \{\
.RS 4
.\}
.nf
add rax, 0xffffffff ; sign\-extended 32\-bit
.fi
.if n \{\
.RE
.\}
.sp
.if n \{\
.RS 4
.\}
.nf
add rax, \-1 ; same as above
.fi
.if n \{\
.RE
.\}
.sp
.if n \{\
.RS 4
.\}
.nf
add rax, 0xffffffffffffffff ; truncated to 32\-bit (warning)
.fi
.if n \{\
.RE
.\}
.sp
.if n \{\
.RS 4
.\}
.nf
mov eax, 1 ; 5 byte
.fi
.if n \{\
.RE
.\}
.sp
.if n \{\
.RS 4
.\}
.nf
mov rax, 1 ; 5 byte (optimized to signed 32\-bit)
.fi
.if n \{\
.RE
.\}
.sp
.if n \{\
.RS 4
.\}
.nf
mov rax, qword 1 ; 10 byte (forced 64\-bit)
.fi
.if n \{\
.RE
.\}
.sp
.if n \{\
.RS 4
.\}
.nf
mov rbx, 0x1234567890abcdef ; 10 byte
.fi
.if n \{\
.RE
.\}
.sp
.if n \{\
.RS 4
.\}
.nf
mov rcx, 0xffffffff ; 10 byte (does not fit in signed 32\-bit)
.fi
.if n \{\
.RE
.\}
.sp
.if n \{\
.RS 4
.\}
.nf
mov ecx, \-1 ; 5 byte, equivalent to above
.fi
.if n \{\
.RE
.\}
.sp
.if n \{\
.RS 4
.\}
.nf
mov rcx, sym ; 5 byte, 32\-bit size default for symbols
.fi
.if n \{\
.RE
.\}
.sp
.if n \{\
.RS 4
.\}
.nf
mov rcx, qword sym ; 10 byte, override default size
.fi
.if n \{\
.RE
.\}
.PP
The handling of mov reg64, unsized immediate is different between YASM and NASM 2\&.x; YASM follows the above behavior, while NASM 2\&.x does the following:
.sp
.if n \{\
.RS 4
.\}
.nf
add rax, 0xffffffff ; sign\-extended 32\-bit immediate
.fi
.if n \{\
.RE
.\}
.sp
.if n \{\
.RS 4
.\}
.nf
add rax, \-1 ; same as above
.fi
.if n \{\
.RE
.\}
.sp
.if n \{\
.RS 4
.\}
.nf
add rax, 0xffffffffffffffff ; truncated 32\-bit (warning)
.fi
.if n \{\
.RE
.\}
.sp
.if n \{\
.RS 4
.\}
.nf
add rax, sym ; sign\-extended 32\-bit immediate
.fi
.if n \{\
.RE
.\}
.sp
.if n \{\
.RS 4
.\}
.nf
mov eax, 1 ; 5 byte (32\-bit immediate)
.fi
.if n \{\
.RE
.\}
.sp
.if n \{\
.RS 4
.\}
.nf
mov rax, 1 ; 10 byte (64\-bit immediate)
.fi
.if n \{\
.RE
.\}
.sp
.if n \{\
.RS 4
.\}
.nf
mov rbx, 0x1234567890abcdef ; 10 byte instruction
.fi
.if n \{\
.RE
.\}
.sp
.if n \{\
.RS 4
.\}
.nf
mov rcx, 0xffffffff ; 10 byte instruction
.fi
.if n \{\
.RE
.\}
.sp
.if n \{\
.RS 4
.\}
.nf
mov ecx, \-1 ; 5 byte, equivalent to above
.fi
.if n \{\
.RE
.\}
.sp
.if n \{\
.RS 4
.\}
.nf
mov ecx, sym ; 5 byte (32\-bit immediate)
.fi
.if n \{\
.RE
.\}
.sp
.if n \{\
.RS 4
.\}
.nf
mov rcx, sym ; 10 byte instruction
.fi
.if n \{\
.RE
.\}
.sp
.if n \{\
.RS 4
.\}
.nf
mov rcx, qword sym ; 10 byte (64\-bit immediate)
.fi
.if n \{\
.RE
.\}
.RE
.sp
.it 1 an-trap
.nr an-no-space-flag 1
.nr an-break-flag 1
.br
.ps +1
\fBDisplacements\fR
.RS 4
.PP
Just like immediates, displacements, for the most part, remain 32 bits and are sign extended prior to use\&. Again, the exception is one restricted form of the mov instruction: between the al/ax/eax/rax register and a 64\-bit absolute address (no registers allowed in the effective address)\&. In NASM syntax, use of the 64\-bit absolute form requires
\fB[qword]\fR\&. Examples in NASM syntax:
.sp
.if n \{\
.RS 4
.\}
.nf
mov eax, [1] ; 32 bit, with sign extension
.fi
.if n \{\
.RE
.\}
.sp
.if n \{\
.RS 4
.\}
.nf
mov al, [rax\-1] ; 32 bit, with sign extension
.fi
.if n \{\
.RE
.\}
.sp
.if n \{\
.RS 4
.\}
.nf
mov al, [qword 0x1122334455667788] ; 64\-bit absolute
.fi
.if n \{\
.RE
.\}
.sp
.if n \{\
.RS 4
.\}
.nf
mov al, [0x1122334455667788] ; truncated to 32\-bit (warning)
.fi
.if n \{\
.RE
.\}
.RE
.sp
.it 1 an-trap
.nr an-no-space-flag 1
.nr an-break-flag 1
.br
.ps +1
\fBRIP Relative Addressing\fR
.RS 4
.PP
In 64\-bit mode, a new form of effective addressing is available to make it easier to write position\-independent code\&. Any memory reference may be made RIP relative (RIP is the instruction pointer register, which contains the address of the location immediately following the current instruction)\&.
.PP
In NASM syntax, there are two ways to specify RIP\-relative addressing:
.sp
.if n \{\
.RS 4
.\}
.nf
mov dword [rip+10], 1
.fi
.if n \{\
.RE
.\}
.PP
stores the value 1 ten bytes after the end of the instruction\&.
\fB10\fR
can also be a symbolic constant, and will be treated the same way\&. On the other hand,
.sp
.if n \{\
.RS 4
.\}
.nf
mov dword [symb wrt rip], 1
.fi
.if n \{\
.RE
.\}
.PP
stores the value 1 into the address of symbol
\fBsymb\fR\&. This is distinctly different than the behavior of:
.sp
.if n \{\
.RS 4
.\}
.nf
mov dword [symb+rip], 1
.fi
.if n \{\
.RE
.\}
.PP
which takes the address of the end of the instruction, adds the address of
\fBsymb\fR
to it, then stores the value 1 there\&. If
\fBsymb\fR
is a variable, this will
\fInot\fR
store the value 1 into the
\fBsymb\fR
variable!
.PP
Yasm also supports the following syntax for RIP\-relative addressing:
.sp
.if n \{\
.RS 4
.\}
.nf
mov [rel sym], rax ; RIP\-relative
.fi
.if n \{\
.RE
.\}
.sp
.if n \{\
.RS 4
.\}
.nf
mov [abs sym], rax ; not RIP\-relative
.fi
.if n \{\
.RE
.\}
.PP
The behavior of:
.sp
.if n \{\
.RS 4
.\}
.nf
mov [sym], rax
.fi
.if n \{\
.RE
.\}
.PP
Depends on a mode set by the DEFAULT directive, as follows\&. The default mode is always "abs", and in "rel" mode, use of registers, an fs or gs segment override, or an explicit "abs" override will result in a non\-RIP\-relative effective address\&.
.sp
.if n \{\
.RS 4
.\}
.nf
default rel
.fi
.if n \{\
.RE
.\}
.sp
.if n \{\
.RS 4
.\}
.nf
mov [sym], rbx ; RIP\-relative
.fi
.if n \{\
.RE
.\}
.sp
.if n \{\
.RS 4
.\}
.nf
mov [abs sym], rbx ; not RIP\-relative (explicit override)
.fi
.if n \{\
.RE
.\}
.sp
.if n \{\
.RS 4
.\}
.nf
mov [rbx+1], rbx ; not RIP\-relative (register use)
.fi
.if n \{\
.RE
.\}
.sp
.if n \{\
.RS 4
.\}
.nf
mov [fs:sym], rbx ; not RIP\-relative (fs or gs use)
.fi
.if n \{\
.RE
.\}
.sp
.if n \{\
.RS 4
.\}
.nf
mov [ds:sym], rbx ; RIP\-relative (segment, but not fs or gs)
.fi
.if n \{\
.RE
.\}
.sp
.if n \{\
.RS 4
.\}
.nf
mov [rel sym], rbx ; RIP\-relative (redundant override)
.fi
.if n \{\
.RE
.\}
.sp
.if n \{\
.RS 4
.\}
.nf
default abs
.fi
.if n \{\
.RE
.\}
.sp
.if n \{\
.RS 4
.\}
.nf
mov [sym], rbx ; not RIP\-relative
.fi
.if n \{\
.RE
.\}
.sp
.if n \{\
.RS 4
.\}
.nf
mov [abs sym], rbx ; not RIP\-relative
.fi
.if n \{\
.RE
.\}
.sp
.if n \{\
.RS 4
.\}
.nf
mov [rbx+1], rbx ; not RIP\-relative
.fi
.if n \{\
.RE
.\}
.sp
.if n \{\
.RS 4
.\}
.nf
mov [fs:sym], rbx ; not RIP\-relative
.fi
.if n \{\
.RE
.\}
.sp
.if n \{\
.RS 4
.\}
.nf
mov [ds:sym], rbx ; not RIP\-relative
.fi
.if n \{\
.RE
.\}
.sp
.if n \{\
.RS 4
.\}
.nf
mov [rel sym], rbx ; RIP\-relative (explicit override)
.fi
.if n \{\
.RE
.\}
.RE
.sp
.it 1 an-trap
.nr an-no-space-flag 1
.nr an-break-flag 1
.br
.ps +1
\fBMemory references\fR
.RS 4
.PP
Usually the size of a memory reference can be deduced by which registers you\*(Aqre moving\-\-for example, "mov [rax],ecx" is a 32\-bit move, because ecx is 32 bits\&. YASM currently gives the non\-obvious "invalid combination of opcode and operands" error if it can\*(Aqt figure out how much memory you\*(Aqre moving\&. The fix in this case is to add a memory size specifier: qword, dword, word, or byte\&.
.PP
Here\*(Aqs a 64\-bit memory move, which sets 8 bytes starting at rax:
.sp
.if n \{\
.RS 4
.\}
.nf
mov qword [rax], 1
.fi
.if n \{\
.RE
.\}
.PP
Here\*(Aqs a 32\-bit memory move, which sets 4 bytes:
.sp
.if n \{\
.RS 4
.\}
.nf
mov dword [rax], 1
.fi
.if n \{\
.RE
.\}
.PP
Here\*(Aqs a 16\-bit memory move, which sets 2 bytes:
.sp
.if n \{\
.RS 4
.\}
.nf
mov word [rax], 1
.fi
.if n \{\
.RE
.\}
.PP
Here\*(Aqs an 8\-bit memory move, which sets 1 byte:
.sp
.if n \{\
.RS 4
.\}
.nf
mov byte [rax], 1
.fi
.if n \{\
.RE
.\}
.RE
.SH "LC3B ARCHITECTURE"
.PP
The
\(lqlc3b\(rq
architecture supports the LC\-3b ISA as used in the ECE 312 (now ECE 411) course at the University of Illinois, Urbana\-Champaign, as well as other university courses\&. See
\m[blue]\fB\%http://courses.ece.uiuc.edu/ece411/\fR\m[]
for more details and example code\&. The
\(lqlc3b\(rq
architecture consists of only one machine:
\(lqlc3b\(rq\&.
.SH "SEE ALSO"
.PP
\fByasm\fR(1)
.SH "BUGS"
.PP
When using the
\(lqx86\(rq
architecture, it is overly easy to generate AMD64 code (using the
\fBBITS 64\fR
directive) and generate a 32\-bit object file (by failing to specify
\fB\-m amd64\fR
on the command line or selecting a 64\-bit object format)\&. Similarly, specifying
\fB\-m amd64\fR
does not default the BITS setting to 64\&. An easy way to avoid this is by directly specifying a 64\-bit object format such as
\fB\-f elf64\fR\&.
.SH "AUTHOR"
.PP
\fBPeter Johnson\fR <\&peter@tortall\&.net\&>
.RS 4
Author.
.RE
.SH "COPYRIGHT"
.br
Copyright \(co 2004, 2005, 2006, 2007 Peter Johnson
.br

View File

@ -0,0 +1,63 @@
'\" t
.\" Title: yasm_dbgfmts
.\" Author: Peter Johnson <peter@tortall.net>
.\" Generator: DocBook XSL Stylesheets v1.75.2 <http://docbook.sf.net/>
.\" Date: October 2006
.\" Manual: Yasm Supported Debug Formats
.\" Source: Yasm
.\" Language: English
.\"
.TH "YASM_DBGFMTS" "7" "October 2006" "Yasm" "Yasm Supported Debug Formats"
.\" -----------------------------------------------------------------
.\" * set default formatting
.\" -----------------------------------------------------------------
.\" disable hyphenation
.nh
.\" disable justification (adjust text to left margin only)
.ad l
.\" -----------------------------------------------------------------
.\" * MAIN CONTENT STARTS HERE *
.\" -----------------------------------------------------------------
.SH "NAME"
yasm_dbgfmts \- Yasm Supported Debugging Formats
.SH "SYNOPSIS"
.HP \w'\fByasm\fR\ 'u
\fByasm\fR \fB\-g\ \fR\fB\fIdbgfmt\fR\fR \fB\fI\&.\&.\&.\fR\fR
.SH "DESCRIPTION"
.PP
The standard Yasm distribution includes a number of modules for different debugging formats\&. The debugging information is embedded into the object file\&. Use of a non\-\(lqnull\(rq
debug format also causes Yasm to output all symbols to the object file (including local symbols)\&.
.PP
The debug format is selected on the
\fByasm\fR(1)
command line by use of the
\fB\-g \fR\fB\fIdbgfmt\fR\fR
command line option\&.
.SH "CV8"
.PP
The CV8 debug format is used by Microsoft Visual Studio 2005 (version 8\&.0) and is completely undocumented, although it bears strong similarities to earlier CodeView formats\&. Yasm\'s support for the CV8 debug format is currently limited to generating assembly\-level line number information (to allow some level of source\-level debugging)\&. The CV8 debug information is stored in the \&.debug$S and \&.debug$T sections of the Win64 object file\&.
.SH "DWARF2"
.PP
The DWARF 2 debug format is a complex, well\-documented standard for debugging information\&. It was created to overcome shortcomings in STABS, allowing for much more detailed and compact descriptions of data structures, data variable movement, and complex language structures such as in C++\&. The debugging information is stored in sections (just like normal program sections) in the object file\&. Yasm supports full pass\-through of DWARF2 debugging information (e\&.g\&. from a C++ compiler), and can also generate assembly\-level line number information\&.
.SH "NULL"
.PP
The
\(lqnull\(rq
debug format is a placeholder; it adds no debugging information to the output file\&.
.SH "STABS"
.PP
The STABS debug format is a poorly documented, semi\-standard format for debugging information in COFF and ELF object files\&. The debugging information is stored as part of the object file\'s symbol table and thus is limited in complexity and scope\&. Despite this, STABS is a common debugging format on older Unix and compatible systems, as well as DJGPP\&.
.SH "SEE ALSO"
.PP
\fByasm\fR(1),
\fByasm_objfmts\fR(7)
.SH "AUTHOR"
.PP
\fBPeter Johnson\fR <\&peter@tortall\&.net\&>
.RS 4
Author.
.RE
.SH "COPYRIGHT"
.br
Copyright \(co 2006 Peter Johnson
.br

View File

@ -0,0 +1,102 @@
'\" t
.\" Title: yasm_objfmts
.\" Author: Peter Johnson <peter@tortall.net>
.\" Generator: DocBook XSL Stylesheets v1.76.1 <http://docbook.sf.net/>
.\" Date: February 2007
.\" Manual: Yasm Supported Object Formats
.\" Source: Yasm
.\" Language: English
.\"
.TH "YASM_OBJFMTS" "7" "February 2007" "Yasm" "Yasm Supported Object Formats"
.\" -----------------------------------------------------------------
.\" * Define some portability stuff
.\" -----------------------------------------------------------------
.\" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.\" http://bugs.debian.org/507673
.\" http://lists.gnu.org/archive/html/groff/2009-02/msg00013.html
.\" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.ie \n(.g .ds Aq \(aq
.el .ds Aq '
.\" -----------------------------------------------------------------
.\" * set default formatting
.\" -----------------------------------------------------------------
.\" disable hyphenation
.nh
.\" disable justification (adjust text to left margin only)
.ad l
.\" -----------------------------------------------------------------
.\" * MAIN CONTENT STARTS HERE *
.\" -----------------------------------------------------------------
.SH "NAME"
yasm_objfmts \- Yasm Supported Object Formats
.SH "SYNOPSIS"
.HP \w'\fByasm\fR\ 'u
\fByasm\fR \fB\-f\ \fR\fB\fIobjfmt\fR\fR \fB\fI\&.\&.\&.\fR\fR
.SH "DESCRIPTION"
.PP
The standard Yasm distribution includes a number of modules for different object formats (Yasm\*(Aqs primary output)\&.
.PP
The object format is selected on the
\fByasm\fR(1)
command line by use of the
\fB\-f \fR\fB\fIobjfmt\fR\fR
command line option\&.
.SH "BIN"
.PP
The
\(lqbin\(rq
object format produces a flat\-format, non\-relocatable binary file\&. It is appropriate for producing DOS \&.COM executables or things like boot blocks\&. It supports only 3 sections and those sections are written in a predefined order to the output file\&.
.SH "COFF"
.PP
The COFF object format is an older relocatable object format used on older Unix and compatible systems, and also (more recently) on the DJGPP development system for DOS\&.
.SH "DBG"
.PP
The
\(lqdbg\(rq
object format is not a
\(lqreal\(rq
object format; the output file it creates simply describes the sequence of calls made to it by Yasm and the final object and symbol table information in a human\-readable text format (that in a normal object format would get processed into that object format\*(Aqs particular binary representation)\&. This object format is not intended for real use, but rather for debugging Yasm\*(Aqs internals\&.
.SH "ELF"
.PP
The ELF object format really comes in three flavors:
\(lqelf32\(rq
(for 32\-bit targets),
\(lqelf64\(rq
(for 64\-bit targets and
\(lqelfx32\(rq
(for x32 targets)\&. ELF is a standard object format in common use on modern Unix and compatible systems (e\&.g\&. Linux, FreeBSD)\&. ELF has complex support for relocatable and shared objects\&.
.SH "MACHO"
.PP
The Mach\-O object format really comes in two flavors:
\(lqmacho32\(rq
(for 32\-bit targets) and
\(lqmacho64\(rq
(for 64\-bit targets)\&. Mach\-O is used as the object format on MacOS X\&. As Yasm currently only supports x86 and AMD64 instruction sets, it can only generate Mach\-O objects for Intel\-based Macs\&.
.SH "RDF"
.PP
The RDOFF2 object format is a simple multi\-section format originally designed for NASM\&. It supports segment references but not WRT references\&. It was designed primarily for simplicity and has minimalistic headers for ease of loading and linking\&. A complete toolchain (linker, librarian, and loader) is distributed with NASM\&.
.SH "WIN32"
.PP
The Win32 object format produces object files compatible with Microsoft compilers (such as Visual C++) that target the 32\-bit x86 Windows platform\&. The object format itself is an extended version of COFF\&.
.SH "WIN64"
.PP
The Win64 object format produces object files compatible with Microsoft compilers that target the 64\-bit
\(lqx64\(rq
Windows platform\&. This format is very similar to the win32 object format, but produces 64\-bit objects\&.
.SH "XDF"
.PP
The XDF object format is essentially a simplified version of COFF\&. It\*(Aqs a multi\-section relocatable format that supports 64\-bit physical and virtual addresses\&.
.SH "SEE ALSO"
.PP
\fByasm\fR(1),
\fByasm_arch\fR(7)
.SH "AUTHOR"
.PP
\fBPeter Johnson\fR <\&peter@tortall\&.net\&>
.RS 4
Author.
.RE
.SH "COPYRIGHT"
.br
Copyright \(co 2006 Peter Johnson
.br

View File

@ -0,0 +1,58 @@
'\" t
.\" Title: yasm_parsers
.\" Author: Peter Johnson <peter@tortall.net>
.\" Generator: DocBook XSL Stylesheets v1.75.2 <http://docbook.sf.net/>
.\" Date: October 2006
.\" Manual: Yasm Supported Parsers
.\" Source: Yasm
.\" Language: English
.\"
.TH "YASM_PARSERS" "7" "October 2006" "Yasm" "Yasm Supported Parsers"
.\" -----------------------------------------------------------------
.\" * set default formatting
.\" -----------------------------------------------------------------
.\" disable hyphenation
.nh
.\" disable justification (adjust text to left margin only)
.ad l
.\" -----------------------------------------------------------------
.\" * MAIN CONTENT STARTS HERE *
.\" -----------------------------------------------------------------
.SH "NAME"
yasm_parsers \- Yasm Supported Parsers (Assembler Syntaxes)
.SH "SYNOPSIS"
.HP \w'\fByasm\fR\ 'u
\fByasm\fR \fB\-p\ \fR\fB\fIparser\fR\fR [\fB\-r\ \fR\fB\fIpreproc\fR\fR] \fB\fI\&.\&.\&.\fR\fR
.SH "DESCRIPTION"
.PP
The standard Yasm distribution includes a number of modules for different parsers (assembler syntaxes)\&.
.PP
The parser is selected on the
\fByasm\fR(1)
command line by use of the
\fB\-p \fR\fB\fIparser\fR\fR
command line option\&.
.SH "NASM PARSER"
.PP
NASM syntax, selected with
\fB\-p nasm\fR, is the most full\-featured syntax supported by Yasm\&. Yasm is nearly 100% compatible with NASM for 16\-bit and 32\-bit x86 code\&. Yasm additionally supports 64\-bit AMD64 code with Yasm extensions to the NASM syntax; see
\fByasm_arch\fR(7)
for details\&. NASM syntax is the Yasm default\&.
.SH "GAS PARSER"
.PP
The GNU Assembler (GAS) is the de\-facto cross\-platform assembler for modern Unix systems, and is used as the backend for the GCC compiler\&. Yasm\'s support for GAS syntax is moderately good, although immature: not all directives are supported, and only 32\-bit x86 and AMD64 architectures are supported\&. Nearly all of the GAS preprocessor is also supported\&. Yasm\'s GAS syntax support is good enough to handle essentially all x86 and AMD64 GCC compiler output\&. The GAS parser can be selected with
\fB\-p gas\fR\&.
.SH "SEE ALSO"
.PP
\fByasm\fR(1),
\fByasm_arch\fR(7)
.SH "AUTHOR"
.PP
\fBPeter Johnson\fR <\&peter@tortall\&.net\&>
.RS 4
Author.
.RE
.SH "COPYRIGHT"
.br
Copyright \(co 2006 Peter Johnson
.br

View File

@ -0,0 +1,13 @@
Copyright (C) 2016 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

View File

@ -0,0 +1,3 @@
danalbert@google.com
enh@google.com
rprichard@google.com

View File

@ -0,0 +1,487 @@
#
# Copyright (C) 2015 The Android Open Source Project
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
from __future__ import annotations
import atexit
import base64
import logging
import os
import re
import subprocess
from typing import Any, Callable
class FindDeviceError(RuntimeError):
pass
class DeviceNotFoundError(FindDeviceError):
def __init__(self, serial: str) -> None:
self.serial = serial
super(DeviceNotFoundError, self).__init__(
'No device with serial {}'.format(serial))
class NoUniqueDeviceError(FindDeviceError):
def __init__(self) -> None:
super(NoUniqueDeviceError, self).__init__('No unique device')
class ShellError(RuntimeError):
def __init__(
self, cmd: list[str], stdout: str, stderr: str, exit_code: int
) -> None:
super(ShellError, self).__init__(
'`{0}` exited with code {1}'.format(cmd, exit_code))
self.cmd = cmd
self.stdout = stdout
self.stderr = stderr
self.exit_code = exit_code
def get_devices(adb_path: str = 'adb') -> list[str]:
with open(os.devnull, 'wb') as devnull:
subprocess.check_call([adb_path, 'start-server'], stdout=devnull,
stderr=devnull)
out = split_lines(
subprocess.check_output([adb_path, 'devices']).decode('utf-8'))
# The first line of `adb devices` just says "List of attached devices", so
# skip that.
devices = []
for line in out[1:]:
if not line.strip():
continue
if 'offline' in line:
continue
serial, _ = re.split(r'\s+', line, maxsplit=1)
devices.append(serial)
return devices
def _get_unique_device(
product: str | None = None, adb_path: str = 'adb'
) -> AndroidDevice:
devices = get_devices(adb_path=adb_path)
if len(devices) != 1:
raise NoUniqueDeviceError()
return AndroidDevice(devices[0], product, adb_path)
def _get_device_by_serial(
serial: str, product: str | None = None, adb_path: str = 'adb'
) -> AndroidDevice:
for device in get_devices(adb_path=adb_path):
if device == serial:
return AndroidDevice(serial, product, adb_path)
raise DeviceNotFoundError(serial)
def get_device(
serial: str | None = None, product: str | None = None, adb_path: str = 'adb'
) -> AndroidDevice:
"""Get a uniquely identified AndroidDevice if one is available.
Raises:
DeviceNotFoundError:
The serial specified by `serial` or $ANDROID_SERIAL is not
connected.
NoUniqueDeviceError:
Neither `serial` nor $ANDROID_SERIAL was set, and the number of
devices connected to the system is not 1. Having 0 connected
devices will also result in this error.
Returns:
An AndroidDevice associated with the first non-None identifier in the
following order of preference:
1) The `serial` argument.
2) The environment variable $ANDROID_SERIAL.
3) The single device connnected to the system.
"""
if serial is not None:
return _get_device_by_serial(serial, product, adb_path)
android_serial = os.getenv('ANDROID_SERIAL')
if android_serial is not None:
return _get_device_by_serial(android_serial, product, adb_path)
return _get_unique_device(product, adb_path=adb_path)
def _get_device_by_type(flag: str, adb_path: str) -> AndroidDevice:
with open(os.devnull, 'wb') as devnull:
subprocess.check_call([adb_path, 'start-server'], stdout=devnull,
stderr=devnull)
try:
serial = subprocess.check_output(
[adb_path, flag, 'get-serialno']).decode('utf-8').strip()
except subprocess.CalledProcessError:
raise RuntimeError('adb unexpectedly returned nonzero')
if serial == 'unknown':
raise NoUniqueDeviceError()
return _get_device_by_serial(serial, adb_path=adb_path)
def get_usb_device(adb_path: str = 'adb') -> AndroidDevice:
"""Get the unique USB-connected AndroidDevice if it is available.
Raises:
NoUniqueDeviceError:
0 or multiple devices are connected via USB.
Returns:
An AndroidDevice associated with the unique USB-connected device.
"""
return _get_device_by_type('-d', adb_path=adb_path)
def get_emulator_device(adb_path: str = 'adb') -> AndroidDevice:
"""Get the unique emulator AndroidDevice if it is available.
Raises:
NoUniqueDeviceError:
0 or multiple emulators are running.
Returns:
An AndroidDevice associated with the unique running emulator.
"""
return _get_device_by_type('-e', adb_path=adb_path)
def split_lines(s: str) -> list[str]:
"""Splits lines in a way that works even on Windows and old devices.
Windows will see \r\n instead of \n, old devices do the same, old devices
on Windows will see \r\r\n.
"""
# rstrip is used here to workaround a difference between splitlines and
# re.split:
# >>> 'foo\n'.splitlines()
# ['foo']
# >>> re.split(r'\n', 'foo\n')
# ['foo', '']
return re.split(r'[\r\n]+', s.rstrip())
def version(adb_path: list[str] | None = None) -> int:
"""Get the version of adb (in terms of ADB_SERVER_VERSION)."""
adb_path = adb_path if adb_path is not None else ['adb']
version_output = subprocess.check_output(adb_path + ['version'], encoding='utf-8')
pattern = r'^Android Debug Bridge version 1.0.(\d+)$'
result = re.match(pattern, version_output.splitlines()[0])
if not result:
return 0
return int(result.group(1))
class AndroidDevice(object):
# Delimiter string to indicate the start of the exit code.
_RETURN_CODE_DELIMITER = 'x'
# Follow any shell command with this string to get the exit
# status of a program since this isn't propagated by adb.
#
# The delimiter is needed because `printf 1; echo $?` would print
# "10", and we wouldn't be able to distinguish the exit code.
_RETURN_CODE_PROBE = [';', 'echo', '{0}$?'.format(_RETURN_CODE_DELIMITER)]
# Maximum search distance from the output end to find the delimiter.
# adb on Windows returns \r\n even if adbd returns \n. Some old devices
# seem to actually return \r\r\n.
_RETURN_CODE_SEARCH_LENGTH = len(
'{0}255\r\r\n'.format(_RETURN_CODE_DELIMITER))
def __init__(
self, serial: str | None, product: str | None = None, adb_path: str = 'adb'
) -> None:
self.serial = serial
self.product = product
self.adb_path = adb_path
self.adb_cmd = [adb_path]
if self.serial is not None:
self.adb_cmd.extend(['-s', self.serial])
if self.product is not None:
self.adb_cmd.extend(['-p', self.product])
self._linesep: str | None = None
self._features: list[str] | None = None
@property
def linesep(self) -> str:
if self._linesep is None:
self._linesep = subprocess.check_output(
self.adb_cmd + ['shell', 'echo'], encoding='utf-8')
return self._linesep
@property
def features(self) -> list[str]:
if self._features is None:
try:
self._features = split_lines(self._simple_call(['features']))
except subprocess.CalledProcessError:
self._features = []
return self._features
def has_shell_protocol(self) -> bool:
return version(self.adb_cmd) >= 35 and 'shell_v2' in self.features
def _make_shell_cmd(self, user_cmd: list[str]) -> list[str]:
command = self.adb_cmd + ['shell'] + user_cmd
if not self.has_shell_protocol():
command += self._RETURN_CODE_PROBE
return command
def _parse_shell_output(self, out: str) -> tuple[int, str]:
"""Finds the exit code string from shell output.
Args:
out: Shell output string.
Returns:
An (exit_code, output_string) tuple. The output string is
cleaned of any additional stuff we appended to find the
exit code.
Raises:
RuntimeError: Could not find the exit code in |out|.
"""
search_text = out
if len(search_text) > self._RETURN_CODE_SEARCH_LENGTH:
# We don't want to search over massive amounts of data when we know
# the part we want is right at the end.
search_text = search_text[-self._RETURN_CODE_SEARCH_LENGTH:]
partition = search_text.rpartition(self._RETURN_CODE_DELIMITER)
if partition[1] == '':
raise RuntimeError('Could not find exit status in shell output.')
result = int(partition[2])
# partition[0] won't contain the full text if search_text was
# truncated, pull from the original string instead.
out = out[:-len(partition[1]) - len(partition[2])]
return result, out
def _simple_call(self, cmd: list[str]) -> str:
logging.info(' '.join(self.adb_cmd + cmd))
return subprocess.check_output(
self.adb_cmd + cmd, stderr=subprocess.STDOUT).decode('utf-8')
def shell(self, cmd: list[str]) -> tuple[str, str]:
"""Calls `adb shell`
Args:
cmd: command to execute as a list of strings.
Returns:
A (stdout, stderr) tuple. Stderr may be combined into stdout
if the device doesn't support separate streams.
Raises:
ShellError: the exit code was non-zero.
"""
exit_code, stdout, stderr = self.shell_nocheck(cmd)
if exit_code != 0:
raise ShellError(cmd, stdout, stderr, exit_code)
return stdout, stderr
def shell_nocheck(self, cmd: list[str]) -> tuple[int, str, str]:
"""Calls `adb shell`
Args:
cmd: command to execute as a list of strings.
Returns:
An (exit_code, stdout, stderr) tuple. Stderr may be combined
into stdout if the device doesn't support separate streams.
"""
cmd = self._make_shell_cmd(cmd)
logging.info(' '.join(cmd))
p = subprocess.Popen(
cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding='utf-8')
stdout, stderr = p.communicate()
if self.has_shell_protocol():
exit_code = p.returncode
else:
exit_code, stdout = self._parse_shell_output(stdout)
return exit_code, stdout, stderr
def shell_popen(
self,
cmd: list[str],
kill_atexit: bool = True,
preexec_fn: Callable[[], None] | None = None,
creationflags: int = 0,
**kwargs: Any,
) -> subprocess.Popen[Any]:
"""Calls `adb shell` and returns a handle to the adb process.
This function provides direct access to the subprocess used to run the
command, without special return code handling. Users that need the
return value must retrieve it themselves.
Args:
cmd: Array of command arguments to execute.
kill_atexit: Whether to kill the process upon exiting.
preexec_fn: Argument forwarded to subprocess.Popen.
creationflags: Argument forwarded to subprocess.Popen.
**kwargs: Arguments forwarded to subprocess.Popen.
Returns:
subprocess.Popen handle to the adb shell instance
"""
command = self.adb_cmd + ['shell'] + cmd
# Make sure a ctrl-c in the parent script doesn't kill gdbserver.
if os.name == 'nt':
creationflags |= subprocess.CREATE_NEW_PROCESS_GROUP
else:
if preexec_fn is None:
preexec_fn = os.setpgrp
elif preexec_fn is not os.setpgrp:
fn = preexec_fn
def _wrapper() -> None:
fn()
os.setpgrp()
preexec_fn = _wrapper
p = subprocess.Popen(command, creationflags=creationflags,
preexec_fn=preexec_fn, **kwargs)
if kill_atexit:
atexit.register(p.kill)
return p
def install(self, filename: str, replace: bool = False) -> str:
cmd = ['install']
if replace:
cmd.append('-r')
cmd.append(filename)
return self._simple_call(cmd)
def push(self, local: str | list[str], remote: str, sync: bool = False) -> str:
"""Transfer a local file or directory to the device.
Args:
local: The local file or directory to transfer.
remote: The remote path to which local should be transferred.
sync: If True, only transfers files that are newer on the host than
those on the device. If False, transfers all files.
Returns:
Output of the command.
"""
cmd = ['push']
if sync:
cmd.append('--sync')
if isinstance(local, str):
cmd.extend([local, remote])
else:
cmd.extend(local)
cmd.append(remote)
return self._simple_call(cmd)
def pull(self, remote: str, local: str) -> str:
return self._simple_call(['pull', remote, local])
def sync(self, directory: str | None = None) -> str:
cmd = ['sync']
if directory is not None:
cmd.append(directory)
return self._simple_call(cmd)
def tcpip(self, port: str) -> str:
return self._simple_call(['tcpip', port])
def usb(self) -> str:
return self._simple_call(['usb'])
def reboot(self) -> str:
return self._simple_call(['reboot'])
def remount(self) -> str:
return self._simple_call(['remount'])
def root(self) -> str:
return self._simple_call(['root'])
def unroot(self) -> str:
return self._simple_call(['unroot'])
def connect(self, host: str) -> str:
return self._simple_call(['connect', host])
def disconnect(self, host: str) -> str:
return self._simple_call(['disconnect', host])
def forward(self, local: str, remote: str) -> str:
return self._simple_call(['forward', local, remote])
def forward_list(self) -> str:
return self._simple_call(['forward', '--list'])
def forward_no_rebind(self, local: str, remote: str) -> str:
return self._simple_call(['forward', '--no-rebind', local, remote])
def forward_remove(self, local: str) -> str:
return self._simple_call(['forward', '--remove', local])
def forward_remove_all(self) -> str:
return self._simple_call(['forward', '--remove-all'])
def reverse(self, remote: str, local: str) -> str:
return self._simple_call(['reverse', remote, local])
def reverse_list(self) -> str:
return self._simple_call(['reverse', '--list'])
def reverse_no_rebind(self, local: str, remote: str) -> str:
return self._simple_call(['reverse', '--no-rebind', local, remote])
def reverse_remove_all(self) -> str:
return self._simple_call(['reverse', '--remove-all'])
def reverse_remove(self, remote: str) -> str:
return self._simple_call(['reverse', '--remove', remote])
def wait(self) -> str:
return self._simple_call(['wait-for-device'])
def get_prop(self, prop_name: str) -> str | None:
output = split_lines(self.shell(['getprop', prop_name])[0])
if len(output) != 1:
raise RuntimeError('Too many lines in getprop output:\n' +
'\n'.join(output))
value = output[0]
if not value.strip():
return None
return value
def set_prop(self, prop_name: str, value: str) -> None:
self.shell(['setprop', prop_name, value])
def logcat(self) -> str:
"""Returns the contents of logcat."""
return self._simple_call(['logcat', '-d'])
def clear_logcat(self) -> None:
"""Clears the logcat buffer."""
self._simple_call(['logcat', '-c'])

View File

@ -0,0 +1,19 @@
[mypy]
check_untyped_defs = true
disallow_any_generics = true
disallow_any_unimported = true
disallow_subclassing_any = true
disallow_untyped_decorators = true
disallow_untyped_defs = true
follow_imports = silent
implicit_reexport = false
namespace_packages = true
no_implicit_optional = true
show_error_codes = true
strict_equality = true
warn_redundant_casts = true
# Can't enable because mypy cannot reason about _get_subprocess_args.
# warn_return_any = true
warn_unreachable = true
warn_unused_configs = true
warn_unused_ignores = true

View File

@ -0,0 +1,32 @@
#
# Copyright (C) 2015 The Android Open Source Project
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
from distutils.core import setup
setup(
name='adb',
version='0.0.1',
description='A Python interface to the Android Debug Bridge.',
license='Apache 2.0',
keywords='adb android',
packages=['adb'],
package_data={'adb': ['py.typed']},
classifiers=[
'Development Status :: 2 - Pre-Alpha',
'Intended Audience :: Developers',
'License :: OSI Approved :: Apache Software License',
]
)

View File

@ -0,0 +1,80 @@
#
# Copyright (C) 2017 The Android Open Source Project
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
import os
import unittest
from unittest.mock import Mock, patch
import adb
class GetDeviceTest(unittest.TestCase):
def setUp(self) -> None:
self.android_serial = os.getenv('ANDROID_SERIAL')
if 'ANDROID_SERIAL' in os.environ:
del os.environ['ANDROID_SERIAL']
def tearDown(self) -> None:
if self.android_serial is not None:
os.environ['ANDROID_SERIAL'] = self.android_serial
else:
if 'ANDROID_SERIAL' in os.environ:
del os.environ['ANDROID_SERIAL']
@patch('adb.get_devices')
def test_explicit(self, mock_get_devices: Mock) -> None:
mock_get_devices.return_value = ['foo', 'bar']
device = adb.get_device('foo')
self.assertEqual(device.serial, 'foo')
@patch('adb.get_devices')
def test_from_env(self, mock_get_devices: Mock) -> None:
mock_get_devices.return_value = ['foo', 'bar']
os.environ['ANDROID_SERIAL'] = 'foo'
device = adb.get_device()
self.assertEqual(device.serial, 'foo')
@patch('adb.get_devices')
def test_arg_beats_env(self, mock_get_devices: Mock) -> None:
mock_get_devices.return_value = ['foo', 'bar']
os.environ['ANDROID_SERIAL'] = 'bar'
device = adb.get_device('foo')
self.assertEqual(device.serial, 'foo')
@patch('adb.get_devices')
def test_no_such_device(self, mock_get_devices: Mock) -> None:
mock_get_devices.return_value = ['foo', 'bar']
self.assertRaises(adb.DeviceNotFoundError, adb.get_device, ['baz'])
os.environ['ANDROID_SERIAL'] = 'baz'
self.assertRaises(adb.DeviceNotFoundError, adb.get_device)
@patch('adb.get_devices')
def test_unique_device(self, mock_get_devices: Mock) -> None:
mock_get_devices.return_value = ['foo']
device = adb.get_device()
self.assertEqual(device.serial, 'foo')
@patch('adb.get_devices')
def test_no_unique_device(self, mock_get_devices: Mock) -> None:
mock_get_devices.return_value = ['foo', 'bar']
self.assertRaises(adb.NoUniqueDeviceError, adb.get_device)
def main() -> None:
suite = unittest.TestLoader().loadTestsFromName(__name__)
unittest.TextTestRunner(verbosity=3).run(suite)
if __name__ == '__main__':
main()

View File

@ -0,0 +1,2 @@
This library provides access to the fastboot utility.
For fastboot bootloader tests, see platform/system/extra/tests/bootloader

View File

@ -0,0 +1,17 @@
#
# Copyright (C) 2016 The Android Open Source Project
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
from __future__ import absolute_import
from .device import * # pylint: disable=wildcard-import

View File

@ -0,0 +1,235 @@
#
# Copyright (C) 2016 The Android Open Source Project
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
"""Provides functionality to interact with a device via `fastboot`."""
import os
import re
import subprocess
class FastbootError(Exception):
"""Something went wrong interacting with fastboot."""
class FastbootDevice(object):
"""Class to interact with a fastboot device."""
# Prefix for INFO-type messages when printed by fastboot. If we want
# to parse the output from an INFO message we need to strip this off.
INFO_PREFIX = '(bootloader) '
def __init__(self, path='fastboot'):
"""Initialization.
Args:
path: path to the fastboot executable to test with.
Raises:
FastbootError: Failed to find a device in fastboot mode.
"""
self.path = path
# Make sure the fastboot executable is available.
try:
_subprocess_check_output([self.path, '--version'])
except OSError:
raise FastbootError('Could not execute `{}`'.format(self.path))
# Make sure exactly 1 fastboot device is available if <specific device>
# was not given as an argument. Do not try to find an adb device and
# put it in fastboot mode, it would be too easy to accidentally
# download to the wrong device.
if not self._check_single_device():
raise FastbootError('Requires exactly 1 device in fastboot mode')
def _check_single_device(self):
"""Returns True if there is exactly one fastboot device attached.
When ANDROID_SERIAL is set it checks that the device is available.
"""
if 'ANDROID_SERIAL' in os.environ:
try:
self.getvar('product')
return True
except subprocess.CalledProcessError:
return False
devices = _subprocess_check_output([self.path, 'devices']).splitlines()
return len(devices) == 1 and devices[0].split()[1] == 'fastboot'
def getvar(self, name):
"""Calls `fastboot getvar`.
To query all variables (fastboot getvar all) use getvar_all()
instead.
Args:
name: variable name to access.
Returns:
String value of variable |name| or None if not found.
"""
try:
output = _subprocess_check_output([self.path, 'getvar', name],
stderr=subprocess.STDOUT).splitlines()
except subprocess.CalledProcessError:
return None
# Output format is <name>:<whitespace><value>.
out = 0
if output[0] == "< waiting for any device >":
out = 1
result = re.search(r'{}:\s*(.*)'.format(name), output[out])
if result:
return result.group(1)
else:
return None
def getvar_all(self):
"""Calls `fastboot getvar all`.
Returns:
A {name, value} dictionary of variables.
"""
output = _subprocess_check_output([self.path, 'getvar', 'all'],
stderr=subprocess.STDOUT).splitlines()
all_vars = {}
for line in output:
result = re.search(r'(.*):\s*(.*)', line)
if result:
var_name = result.group(1)
# `getvar all` works by sending one INFO message per variable
# so we need to strip out the info prefix string.
if var_name.startswith(self.INFO_PREFIX):
var_name = var_name[len(self.INFO_PREFIX):]
# In addition to returning all variables the bootloader may
# also think it's supposed to query a return a variable named
# "all", so ignore this line if so. Fastboot also prints a
# summary line that we want to ignore.
if var_name != 'all' and 'total time' not in var_name:
all_vars[var_name] = result.group(2)
return all_vars
def flashall(self, wipe_user=True, slot=None, skip_secondary=False, quiet=True):
"""Calls `fastboot [-w] flashall`.
Args:
wipe_user: whether to set the -w flag or not.
slot: slot to flash if device supports A/B, otherwise default will be used.
skip_secondary: on A/B devices, flashes only the primary images if true.
quiet: True to hide output, false to send it to stdout.
"""
func = (_subprocess_check_output if quiet else subprocess.check_call)
command = [self.path, 'flashall']
if slot:
command.extend(['--slot', slot])
if skip_secondary:
command.append("--skip-secondary")
if wipe_user:
command.append('-w')
func(command, stderr=subprocess.STDOUT)
def flash(self, partition='cache', img=None, slot=None, quiet=True):
"""Calls `fastboot flash`.
Args:
partition: which partition to flash.
img: path to .img file, otherwise the default will be used.
slot: slot to flash if device supports A/B, otherwise default will be used.
quiet: True to hide output, false to send it to stdout.
"""
func = (_subprocess_check_output if quiet else subprocess.check_call)
command = [self.path, 'flash', partition]
if img:
command.append(img)
if slot:
command.extend(['--slot', slot])
if skip_secondary:
command.append("--skip-secondary")
func(command, stderr=subprocess.STDOUT)
def reboot(self, bootloader=False):
"""Calls `fastboot reboot [bootloader]`.
Args:
bootloader: True to reboot back to the bootloader.
"""
command = [self.path, 'reboot']
if bootloader:
command.append('bootloader')
_subprocess_check_output(command, stderr=subprocess.STDOUT)
def set_active(self, slot):
"""Calls `fastboot set_active <slot>`.
Args:
slot: The slot to set as the current slot."""
command = [self.path, 'set_active', slot]
_subprocess_check_output(command, stderr=subprocess.STDOUT)
# If necessary, modifies subprocess.check_output() or subprocess.Popen() args
# to run the subprocess via Windows PowerShell to work-around an issue in
# Python 2's subprocess class on Windows where it doesn't support Unicode.
def _get_subprocess_args(args):
# Only do this slow work-around if Unicode is in the cmd line on Windows.
# PowerShell takes 600-700ms to startup on a 2013-2014 machine, which is
# very slow.
if os.name != 'nt' or all(not isinstance(arg, unicode) for arg in args[0]):
return args
def escape_arg(arg):
# Escape for the parsing that the C Runtime does in Windows apps. In
# particular, this will take care of double-quotes.
arg = subprocess.list2cmdline([arg])
# Escape single-quote with another single-quote because we're about
# to...
arg = arg.replace(u"'", u"''")
# ...put the arg in a single-quoted string for PowerShell to parse.
arg = u"'" + arg + u"'"
return arg
# Escape command line args.
argv = map(escape_arg, args[0])
# Cause script errors (such as adb not found) to stop script immediately
# with an error.
ps_code = u'$ErrorActionPreference = "Stop"\r\n'
# Add current directory to the PATH var, to match cmd.exe/CreateProcess()
# behavior.
ps_code += u'$env:Path = ".;" + $env:Path\r\n'
# Precede by &, the PowerShell call operator, and separate args by space.
ps_code += u'& ' + u' '.join(argv)
# Make the PowerShell exit code the exit code of the subprocess.
ps_code += u'\r\nExit $LastExitCode'
# Encode as UTF-16LE (without Byte-Order-Mark) which Windows natively
# understands.
ps_code = ps_code.encode('utf-16le')
# Encode the PowerShell command as base64 and use the special
# -EncodedCommand option that base64 decodes. Base64 is just plain ASCII,
# so it should have no problem passing through Win32 CreateProcessA()
# (which python erroneously calls instead of CreateProcessW()).
return (['powershell.exe', '-NoProfile', '-NonInteractive',
'-EncodedCommand', base64.b64encode(ps_code)],) + args[1:]
# Call this instead of subprocess.check_output() to work-around issue in Python
# 2's subprocess class on Windows where it doesn't support Unicode.
def _subprocess_check_output(*args, **kwargs):
try:
return subprocess.check_output(*_get_subprocess_args(args), **kwargs)
except subprocess.CalledProcessError as e:
# Show real command line instead of the powershell.exe command line.
raise subprocess.CalledProcessError(e.returncode, args[0],
output=e.output)

View File

@ -0,0 +1,32 @@
#
# Copyright (C) 2016 The Android Open Source Project
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
from distutils.core import setup
setup(
name='fastboot',
version='0.0.1',
description='A Python interface to the Fastboot utility.',
license='Apache 2.0',
keywords='fastboot android',
package_dir={'fastboot': ''},
packages=['fastboot'],
classifiers=[
'Development Status :: 2 - Pre-Alpha',
'Intended Audience :: Developers',
'License :: OSI Approved :: Apache Software License',
]
)

View File

@ -0,0 +1 @@
danalbert@google.com

View File

@ -0,0 +1,64 @@
# fetchartifact
This is a Python interface to http://go/fetchartifact, which is used for
fetching artifacts from http://go/ab.
## Usage
```python
from fetchartifact import fetchartifact
async def main() -> None:
artifacts = await fetch_artifact(
branch="aosp-master-ndk",
target="linux",
build="1234",
pattern="android-ndk-*.zip",
)
for artifact in artifacts:
print(f"Downloaded {artifact}")
```
## Development
For first time set-up, install https://python-poetry.org/, then run
`poetry install` to install the project's dependencies.
This project uses mypy and pylint for linting, black and isort for
auto-formatting, and pytest for testing. All of these tools will be installed
automatically, but you may want to configure editor integration for them.
To run any of the tools poetry installed, you can either prefix all your
commands with `poetry run` (as in `poetry run pytest`), or you can run
`poetry shell` to enter a shell with all the tools on the `PATH`. The following
instructions assume you've run `poetry shell` first.
To run the linters:
```bash
mypy fetchartifact tests
pylint fetchartifact tests
```
To auto-format the code (though I recommend configuring your editor to do this
on save):
```bash
isort .
black .
```
To run the tests and generate coverage:
```bash
pytest --cov=fetchartifact
```
Optionally, pass `--cov-report=html` to generate an HTML report, or
`--cov-report=xml` to generate an XML report for your editor.
Some tests require network access. If you need to run the tests in an
environment that cannot access the Android build servers, add
`-m "not requires_network"` to skip those tests. Only a mock service can be
tested without network access.

View File

@ -0,0 +1,112 @@
#
# Copyright (C) 2023 The Android Open Source Project
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
"""A Python interface to https://android.googlesource.com/tools/fetch_artifact/."""
import logging
import urllib
from collections.abc import AsyncIterable
from logging import Logger
from typing import cast
from aiohttp import ClientSession
_DEFAULT_QUERY_URL_BASE = "https://androidbuildinternal.googleapis.com"
def _logger() -> Logger:
return logging.getLogger("fetchartifact")
def _make_download_url(
target: str,
build_id: str,
artifact_name: str,
query_url_base: str,
) -> str:
"""Constructs the download URL.
Args:
target: Name of the build target from which to fetch the artifact.
build_id: ID of the build from which to fetch the artifact.
artifact_name: Name of the artifact to fetch.
Returns:
URL for the given artifact.
"""
# The Android build API does not handle / in artifact names, but urllib.parse.quote
# thinks those are safe by default. We need to escape them.
artifact_name = urllib.parse.quote(artifact_name, safe="")
return (
f"{query_url_base}/android/internal/build/v3/builds/{build_id}/{target}/"
f"attempts/latest/artifacts/{artifact_name}/url"
)
async def fetch_artifact(
target: str,
build_id: str,
artifact_name: str,
session: ClientSession,
query_url_base: str = _DEFAULT_QUERY_URL_BASE,
) -> bytes:
"""Fetches an artifact from the build server.
Args:
target: Name of the build target from which to fetch the artifact.
build_id: ID of the build from which to fetch the artifact.
artifact_name: Name of the artifact to fetch.
session: The aiohttp ClientSession to use. If omitted, one will be created and
destroyed for every call.
query_url_base: The base of the endpoint used for querying download URLs. Uses
the android build service by default, but can be replaced for testing.
Returns:
The bytes of the downloaded artifact.
"""
download_url = _make_download_url(target, build_id, artifact_name, query_url_base)
_logger().debug("Beginning download from %s", download_url)
async with session.get(download_url) as response:
response.raise_for_status()
return await response.read()
async def fetch_artifact_chunked(
target: str,
build_id: str,
artifact_name: str,
session: ClientSession,
chunk_size: int = 16 * 1024 * 1024,
query_url_base: str = _DEFAULT_QUERY_URL_BASE,
) -> AsyncIterable[bytes]:
"""Fetches an artifact from the build server.
Args:
target: Name of the build target from which to fetch the artifact.
build_id: ID of the build from which to fetch the artifact.
artifact_name: Name of the artifact to fetch.
session: The aiohttp ClientSession to use. If omitted, one will be created and
destroyed for every call.
query_url_base: The base of the endpoint used for querying download URLs. Uses
the android build service by default, but can be replaced for testing.
Returns:
Async iterable bytes of the artifact contents.
"""
download_url = _make_download_url(target, build_id, artifact_name, query_url_base)
_logger().debug("Beginning download from %s", download_url)
async with session.get(download_url) as response:
response.raise_for_status()
async for chunk in response.content.iter_chunked(chunk_size):
yield chunk

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,60 @@
[tool.poetry]
name = "fetchartifact"
version = "0.1.0"
description = "Python library for https://android.googlesource.com/tools/fetch_artifact/."
authors = ["The Android Open Source Project"]
license = "Apache-2.0"
[tool.poetry.dependencies]
python = "^3.10"
aiohttp = "^3.8.4"
[tool.poetry.group.dev.dependencies]
mypy = "^1.2.0"
pylint = "^2.17.2"
black = "^23.3.0"
pytest = "^7.2.2"
pytest-asyncio = "^0.21.0"
isort = "^5.12.0"
pytest-aiohttp = "^1.0.4"
pytest-cov = "^4.0.0"
[tool.coverage.report]
fail_under = 100
[tool.pytest.ini_options]
addopts = "--strict-markers"
asyncio_mode = "auto"
markers = [
"requires_network: marks a test that requires network access"
]
xfail_strict = true
[tool.mypy]
check_untyped_defs = true
disallow_any_generics = true
disallow_any_unimported = true
disallow_subclassing_any = true
disallow_untyped_decorators = true
disallow_untyped_defs = true
follow_imports = "silent"
implicit_reexport = false
namespace_packages = true
no_implicit_optional = true
show_error_codes = true
strict_equality = true
warn_redundant_casts = true
warn_return_any = true
warn_unreachable = true
warn_unused_configs = true
warn_unused_ignores = true
[tool.pylint."MESSAGES CONTROL"]
disable = "duplicate-code,too-many-arguments"
[tool.isort]
profile = "black"
[build-system]
requires = ["poetry-core>=1.0.0"]
build-backend = "poetry.core.masonry.api"

View File

@ -0,0 +1,15 @@
#
# Copyright (C) 2023 The Android Open Source Project
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#

View File

@ -0,0 +1,101 @@
#
# Copyright (C) 2023 The Android Open Source Project
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
"""Tests for fetchartifact."""
from typing import cast
import pytest
from aiohttp import ClientResponseError, ClientSession
from aiohttp.test_utils import TestClient
from aiohttp.web import Application, Request, Response
from fetchartifact import fetch_artifact, fetch_artifact_chunked
TEST_BUILD_ID = "1234"
TEST_TARGET = "linux"
TEST_ARTIFACT_NAME = "output.zip"
TEST_DOWNLOAD_URL = (
f"/android/internal/build/v3/builds/{TEST_BUILD_ID}/{TEST_TARGET}/"
f"attempts/latest/artifacts/{TEST_ARTIFACT_NAME}/url"
)
TEST_RESPONSE = b"Hello, world!"
@pytest.fixture(name="android_ci_client")
async def fixture_android_ci_client(aiohttp_client: type[TestClient]) -> TestClient:
"""Fixture for mocking the Android CI APIs."""
async def download(_request: Request) -> Response:
return Response(text=TEST_RESPONSE.decode("utf-8"))
app = Application()
app.router.add_get(TEST_DOWNLOAD_URL, download)
return await aiohttp_client(app) # type: ignore
async def test_fetch_artifact(android_ci_client: TestClient) -> None:
"""Tests that the download URL is queried."""
assert TEST_RESPONSE == await fetch_artifact(
TEST_TARGET,
TEST_BUILD_ID,
TEST_ARTIFACT_NAME,
cast(ClientSession, android_ci_client),
query_url_base="",
)
async def test_fetch_artifact_chunked(android_ci_client: TestClient) -> None:
"""Tests that the full file contents are downloaded."""
assert [c.encode("utf-8") for c in TEST_RESPONSE.decode("utf-8")] == [
chunk
async for chunk in fetch_artifact_chunked(
TEST_TARGET,
TEST_BUILD_ID,
TEST_ARTIFACT_NAME,
cast(ClientSession, android_ci_client),
chunk_size=1,
query_url_base="",
)
]
async def test_failure_raises(android_ci_client: TestClient) -> None:
"""Tests that fetch failure raises an exception."""
with pytest.raises(ClientResponseError):
await fetch_artifact(
TEST_TARGET,
TEST_BUILD_ID,
TEST_ARTIFACT_NAME,
cast(ClientSession, android_ci_client),
query_url_base="/bad",
)
with pytest.raises(ClientResponseError):
async for _chunk in fetch_artifact_chunked(
TEST_TARGET,
TEST_BUILD_ID,
TEST_ARTIFACT_NAME,
cast(ClientSession, android_ci_client),
query_url_base="/bad",
):
pass
@pytest.mark.requires_network
async def test_real_artifact() -> None:
"""Tests with a real artifact. Requires an internet connection."""
async with ClientSession() as session:
contents = await fetch_artifact("linux", "9945621", "logs/SUCCEEDED", session)
assert contents == b"1681499053\n"

View File

@ -0,0 +1,399 @@
#
# Copyright (C) 2015 The Android Open Source Project
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
"""Helpers used by both gdbclient.py and ndk-gdb.py."""
import adb
import argparse
import atexit
import os
import re
import shlex
import subprocess
import sys
import tempfile
class ArgumentParser(argparse.ArgumentParser):
"""ArgumentParser subclass that provides adb device selection."""
def __init__(self):
super(ArgumentParser, self).__init__()
self.add_argument(
"--adb", dest="adb_path",
help="use specific adb command")
group = self.add_argument_group(title="device selection")
group = group.add_mutually_exclusive_group()
group.add_argument(
"-a", action="store_const", dest="device", const="-a",
help="directs commands to all interfaces")
group.add_argument(
"-d", action="store_const", dest="device", const="-d",
help="directs commands to the only connected USB device")
group.add_argument(
"-e", action="store_const", dest="device", const="-e",
help="directs commands to the only connected emulator")
group.add_argument(
"-s", metavar="SERIAL", action="store", dest="serial",
help="directs commands to device/emulator with the given serial")
def parse_args(self, args=None, namespace=None):
result = super(ArgumentParser, self).parse_args(args, namespace)
adb_path = result.adb_path or "adb"
# Try to run the specified adb command
try:
subprocess.check_output([adb_path, "version"],
stderr=subprocess.STDOUT)
except (OSError, subprocess.CalledProcessError):
msg = "ERROR: Unable to run adb executable (tried '{}')."
if not result.adb_path:
msg += "\n Try specifying its location with --adb."
sys.exit(msg.format(adb_path))
try:
if result.device == "-a":
result.device = adb.get_device(adb_path=adb_path)
elif result.device == "-d":
result.device = adb.get_usb_device(adb_path=adb_path)
elif result.device == "-e":
result.device = adb.get_emulator_device(adb_path=adb_path)
else:
result.device = adb.get_device(result.serial, adb_path=adb_path)
except (adb.DeviceNotFoundError, adb.NoUniqueDeviceError, RuntimeError):
# Don't error out if we can't find a device.
result.device = None
return result
def get_processes(device):
"""Return a dict from process name to list of running PIDs on the device."""
# Some custom ROMs use busybox instead of toolbox for ps. Without -w,
# busybox truncates the output, and very long package names like
# com.exampleisverylongtoolongbyfar.plasma exceed the limit.
#
# API 26 use toybox instead of toolbox for ps and needs -A to list
# all processes.
#
# Perform the check for this on the device to avoid an adb roundtrip
# Some devices might not have readlink or which, so we need to handle
# this as well.
#
# Gracefully handle [ or readlink being missing by always using `ps` if
# readlink is missing. (API 18 has [, but not readlink).
ps_script = """
if $(ls /system/bin/readlink >/dev/null 2>&1); then
if [ $(readlink /system/bin/ps) == "busybox" ]; then
ps -w;
elif [ $(readlink /system/bin/ps) == "toybox" ]; then
ps -A;
else
ps;
fi
else
ps;
fi
"""
ps_script = " ".join([line.strip() for line in ps_script.splitlines()])
output, _ = device.shell([ps_script])
return parse_ps_output(output)
def parse_ps_output(output):
processes = dict()
output = adb.split_lines(output.replace("\r", ""))
columns = output.pop(0).split()
try:
pid_column = columns.index("PID")
except ValueError:
pid_column = 1
while output:
columns = output.pop().split()
process_name = columns[-1]
pid = int(columns[pid_column])
if process_name in processes:
processes[process_name].append(pid)
else:
processes[process_name] = [pid]
return processes
def get_pids(device, process_name):
processes = get_processes(device)
return processes.get(process_name, [])
def start_gdbserver(device, gdbserver_local_path, gdbserver_remote_path,
target_pid, run_cmd, debug_socket, port, run_as_cmd=[],
lldb=False, chroot=""):
"""Start gdbserver in the background and forward necessary ports.
Args:
device: ADB device to start gdbserver on.
gdbserver_local_path: Host path to push gdbserver from, can be None.
gdbserver_remote_path: Device path to push gdbserver to.
target_pid: PID of device process to attach to.
run_cmd: Command to run on the device.
debug_socket: Device path to place gdbserver unix domain socket.
port: Host port to forward the debug_socket to.
run_as_cmd: run-as or su command to prepend to commands.
Returns:
Popen handle to the `adb shell` process gdbserver was started with.
"""
assert target_pid is None or run_cmd is None
if chroot:
run_as_cmd = ["chroot", chroot] + run_as_cmd
# Remove the old socket file.
device.shell_nocheck(run_as_cmd + ["rm", debug_socket])
# Push gdbserver to the target.
if gdbserver_local_path is not None:
try:
device.push(gdbserver_local_path, chroot + gdbserver_remote_path)
# If the user here is potentially on Windows, adb cannot inspect execute
# permissions. Since we don't know where the users are, chmod
# gdbserver_remote_path on device regardless.
device.shell(["chmod", "+x", gdbserver_remote_path])
except subprocess.CalledProcessError as err:
print("Command failed:")
print(shlex.join(err.cmd))
print("Output:")
print(err.output.decode("utf-8"))
raise
# Run gdbserver.
gdbserver_cmd = [gdbserver_remote_path]
if lldb:
gdbserver_cmd.extend(["gdbserver", "unix://" + debug_socket])
else:
gdbserver_cmd.extend(["--once", "+{}".format(debug_socket)])
if target_pid is not None:
gdbserver_cmd += ["--attach", str(target_pid)]
else:
gdbserver_cmd += ["--"] + run_cmd
forward_gdbserver_port(device, local=port, remote="localfilesystem:{}".format(chroot + debug_socket))
gdbserver_cmd = run_as_cmd + gdbserver_cmd
if lldb:
gdbserver_output_path = os.path.join(tempfile.gettempdir(),
"lldb-client.log")
print("Redirecting lldb-server output to {}".format(gdbserver_output_path))
else:
gdbserver_output_path = os.path.join(tempfile.gettempdir(),
"gdbclient.log")
print("Redirecting gdbserver output to {}".format(gdbserver_output_path))
gdbserver_output = open(gdbserver_output_path, 'w')
return device.shell_popen(gdbserver_cmd, stdout=gdbserver_output,
stderr=gdbserver_output)
def get_uid(device):
"""Gets the uid adbd runs as."""
line, _ = device.shell(["id", "-u"])
return int(line.strip())
def forward_gdbserver_port(device, local, remote):
"""Forwards local TCP port `port` to `remote` via `adb forward`."""
if get_uid(device) != 0:
WARNING = '\033[93m'
ENDC = '\033[0m'
print(WARNING +
"Port forwarding may not work because adbd is not running as root. " +
" Run `adb root` to fix." + ENDC)
device.forward("tcp:{}".format(local), remote)
atexit.register(lambda: device.forward_remove("tcp:{}".format(local)))
def find_file(device, executable_path, sysroot, run_as_cmd=None):
"""Finds a device executable file.
This function first attempts to find the local file which will
contain debug symbols. If that fails, it will fall back to
downloading the stripped file from the device.
Args:
device: the AndroidDevice object to use.
executable_path: absolute path to the executable or symlink.
sysroot: absolute path to the built symbol sysroot.
run_as_cmd: if necessary, run-as or su command to prepend
Returns:
A tuple containing (<open file object>, <was found locally>).
Raises:
RuntimeError: could not find the executable binary.
ValueError: |executable_path| is not absolute.
"""
if not os.path.isabs(executable_path):
raise ValueError("'{}' is not an absolute path".format(executable_path))
def generate_files():
"""Yields (<file name>, <found locally>) tuples."""
# First look locally to avoid shelling into the device if possible.
# os.path.join() doesn't combine absolute paths, use + instead.
yield (sysroot + executable_path, True)
# Next check if the path is a symlink.
try:
target = device.shell(['readlink', '-e', '-n', executable_path])[0]
yield (sysroot + target, True)
except adb.ShellError:
pass
# Last, download the stripped executable from the device if necessary.
file_name = "gdbclient-binary-{}".format(os.getppid())
remote_temp_path = "/data/local/tmp/{}".format(file_name)
local_path = os.path.join(tempfile.gettempdir(), file_name)
cmd = ["cat", executable_path, ">", remote_temp_path]
if run_as_cmd:
cmd = run_as_cmd + cmd
try:
device.shell(cmd)
except adb.ShellError:
raise RuntimeError("Failed to copy '{}' to temporary folder on "
"device".format(executable_path))
device.pull(remote_temp_path, local_path)
yield (local_path, False)
for path, found_locally in generate_files():
if os.path.isfile(path):
return (open(path, "rb"), found_locally)
raise RuntimeError('Could not find executable {}'.format(executable_path))
def find_executable_path(device, executable_name, run_as_cmd=None):
"""Find a device executable from its name
This function calls which on the device to retrieve the absolute path of
the executable.
Args:
device: the AndroidDevice object to use.
executable_name: the name of the executable to find.
run_as_cmd: if necessary, run-as or su command to prepend
Returns:
The absolute path of the executable.
Raises:
RuntimeError: could not find the executable.
"""
cmd = ["which", executable_name]
if run_as_cmd:
cmd = run_as_cmd + cmd
try:
output, _ = device.shell(cmd)
return adb.split_lines(output)[0]
except adb.ShellError:
raise RuntimeError("Could not find executable '{}' on "
"device".format(executable_name))
def find_binary(device, pid, sysroot, run_as_cmd=None):
"""Finds a device executable file corresponding to |pid|."""
return find_file(device, "/proc/{}/exe".format(pid), sysroot, run_as_cmd)
def get_binary_arch(binary_file):
"""Parse a binary's ELF header for arch."""
try:
binary_file.seek(0)
binary = binary_file.read(0x14)
except IOError:
raise RuntimeError("failed to read binary file")
ei_class = binary[0x4] # 1 = 32-bit, 2 = 64-bit
ei_data = binary[0x5] # Endianness
assert ei_class == 1 or ei_class == 2
if ei_data != 1:
raise RuntimeError("binary isn't little-endian?")
e_machine = binary[0x13] << 8 | binary[0x12]
if e_machine == 0x28:
assert ei_class == 1
return "arm"
elif e_machine == 0xB7:
assert ei_class == 2
return "arm64"
elif e_machine == 0x03:
assert ei_class == 1
return "x86"
elif e_machine == 0x3E:
assert ei_class == 2
return "x86_64"
elif e_machine == 0xF3:
assert ei_class == 2
return "riscv64"
else:
raise RuntimeError("unknown architecture: 0x{:x}".format(e_machine))
def get_binary_interp(binary_path, llvm_readobj_path):
args = [llvm_readobj_path, "--elf-output-style=GNU", "-l", binary_path]
output = subprocess.check_output(args, universal_newlines=True)
m = re.search(r"\[Requesting program interpreter: (.*?)\]\n", output)
if m is None:
return None
else:
return m.group(1)
def start_gdb(gdb_path, gdb_commands, gdb_flags=None, lldb=False):
"""Start gdb in the background and block until it finishes.
Args:
gdb_path: Path of the gdb binary.
gdb_commands: Contents of GDB script to run.
gdb_flags: List of flags to append to gdb command.
"""
# Windows disallows opening the file while it's open for writing.
script_fd, script_path = tempfile.mkstemp()
os.write(script_fd, gdb_commands.encode())
os.close(script_fd)
if lldb:
script_parameter = "--source"
else:
script_parameter = "-x"
gdb_args = [gdb_path, script_parameter, script_path] + (gdb_flags or [])
creationflags = 0
if sys.platform.startswith("win"):
creationflags = subprocess.CREATE_NEW_CONSOLE
gdb_process = subprocess.Popen(gdb_args, creationflags=creationflags)
while gdb_process.returncode is None:
try:
gdb_process.communicate()
except KeyboardInterrupt:
pass
os.unlink(script_path)

View File

@ -0,0 +1,30 @@
#
# Copyright (C) 2023 The Android Open Source Project
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
from setuptools import setup
setup(
version='0.0.1',
description='Common helpers of ndk-gdb and gdbclient.',
license='Apache 2.0',
packages=['gdbrunner'],
package_data={'gdbrunner': ['py.typed']},
classifiers=[
'Development Status :: 2 - Pre-Alpha',
'Intended Audience :: Developers',
'License :: OSI Approved :: Apache Software License',
]
)

View File

@ -0,0 +1,27 @@
#!/usr/bin/env sh
# Copyright (c) 2016 The Khronos Group Inc.
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# A script for automatically disassembling a .spv file
# for less(1). This assumes spirv-dis is on our PATH.
#
# See https://github.com/KhronosGroup/SPIRV-Tools/issues/359
case "$1" in
*.spv) spirv-dis "$@" 2>/dev/null;;
*) exit 1;;
esac
exit $?

View File

@ -0,0 +1,275 @@
build 11421629 (Feb 8, 2024)
inject command:
Support converting LBR profiles to AutoFDO format
list command:
Use event_table.json to config raw events supported on different cpu models, and read
/proc/cpuinfo to know cpu models on device. As a result, we can precisely report raw
events supported on different cpu cores.
record/stat command:
Support monitoring different events on different cores using --cpu
Use thousand groups when reporting sample counts
record command:
Add --delay to delay recording samples
Add --record-timestamp and --record-cycles to record timestamps and cpu-cycles with ETM data
Add --cycle-threshold for ETM cycle count packets
Support reading symbols from DEX files in memory
Support starting simpleperf at early-init when boot profiling, as in android_platform_profiling.md
stat command:
Add --tp-filter and --kprobe to improve tracepoint event counting
app_profiler.py:
Add app versioncode in record file
Add --launch to start an app with its package name
report scripts:
Add --cpu to filter samples based on CPUs
Add ipc.py to capture instructions per cycle of the system
Support profiles generated by the report-sample command
Add sample_filter.py to split large record files
gecko_profile_generator.py:
Add --percpu-samples to show samples grouped by CPUs
report_html.py:
Support parsing kernel disassembly in scripts
Speed up generating disassembly
Sort functions by names in flamegraph
build 10661963 (Aug 16, 2023)
report-sample command: Remove small stack gaps to get a smoother view in Stack Chart.
Use prebult libsimpleperf_readelf (provided by Android clang prebuilts) for reading ELF files.
report_html.py: Speed up disassembling many functions in a binary.
build 10306210 (Jun 12, 2023)
record cmd: Add --decode-etm to decode ETM data while recording.
It saves the space storing raw ETM data.
Store lost/cut record info in recording file.
Report lost samples in kernel space and user space.
inject cmd: Accept missing aux data.
Add build id in AutoFDO output.
gecko_profile_generatory.py: color off-cpu frames blue and jit app cache frames green.
build 9796343 (March 22, 2023)
Fix dozens of security bugs detected by fuzzer.
record cmd:
Increase the default user buffer size from 64M to 256M for devices having >= 4G memory. This is
to reduce lost samples and incomplete callchains.
Add --user-buffer-size to adjust the user buffer size.
record/stat cmd:
Support using process name regex to select processes via the -p option.
Raise file desciptors limit to better support "stat --per-thread -a".
report:
Support demangling rust symbols.
Suppress read symbol warnings for not-ELF files.
Improve deobfuscating Java symbols (ndk issue 1836).
In report scripts, add --aggregate-threads to merge samples for selected threads.
In binary_cache_builder.py, support searching for binaries having different names from those
recorded in perf.data. Also fix a bug supporting native libs embedded in apk.
In gecko_profile_generator.py, remove small stack gaps to get a smoother view in Stack Chart.
doc:
Update doc on the broken DWARF call graph issue.
Add doc for trying the latest simpleperf builds and scripts.
build 9042912 (Sep 8, 2022)
Fix adhoc codesign for darwin binaries.
Release protobuf files in proto directory.
stat cmd: Update to work with CPU cores having different numbers of PMU counters.
doc: Update collect_etm_data_for_autofdo.md.
build 8355685 (March 25, 2022)
1. Add doc for getting boot-time profile, add doc view_the_profile.md.
2. Share report lib options between scripts.
build 8121221 (Jan 26, 2022)
1. On Android >= 13, allow an app profiling itself even after device reboot.
The permission expiration time can be set by --days in api_profiler.py.
2. Improve --trace-offcpu in record cmd to support multiple report modes: pure on cpu samples,
pure on-cpu samples, or both on-cpu and off-cpu samples.
3. Add --add-counter in record cmd to add additional event counts in samples.
4. Add --filter-file in report cmds and scripts to filter samples based on timestamps.
5. On Android >= 13, add boot-record cmd to record boot-time profiles on userdebug/eng devices.
6. For AutoFDO/ETM support, support multiple input files in inject cmd to speed up processing and
combine output.
build 7848450
record cmd:
Add support for new perf ETE files.
report cmd:
Extend --percent-limit to report entries.
scripts:
Add gecko_profile_generator.py, which generates reports for firefox profiler.
Add stackcollapse.py, which generates reports to Folded Stacks.
Improve support of proguard mapping file.
Improve logging.
pprof_proto_generator.py:
Add thread, threadpool, pid, tid labels.
Set units of common events.
Add comments.
app_profiler.py: Kill app process for current user.
report_sample.py: add --header and --comm options.
doc:
Add introduction slide.
Use auto-generated tables of contents.
test: Fix flaky tests.
build 7649958
Build arm simpleperf for armv7-neon instead of armv8.
Support JIT method name with signature.
Doc improvement.
build 7549687
Use multithreading to speed up line annotation.
Add doc/debug_dwarf_unwinding.md.
Add doc/collect_etm_data_for_autofdo.md.
Move to file2 feature section.
build 7414587
Drop python2 support, scripts are tested on python3.8 and python3.9.
Refactor debug unwinding:
1. Add --keep-failed-unwinding-result and --keep-failed-unwinding-stack options
in record cmd, to generate additional records for failed unwinding cases.
2. Refactor debug-unwind cmd and debug_unwind_reporter.py to report failed
unwinding cases.
Support recording and converting kernel ETM data in record cmd and inject cmd.
Support using proguard mapping file for reporting.
Support vmlinux file when building binary_cache.
Support showing disassembly of vmlinux file in report_html.py. Use multithreading
to speedup disassembling.
Add app_type, android_sdk_version and android_build_type in meta_info of recording file.
ndk r23
build 7173446
Add visualization tool purgatorio.
Switch to llvm-objdump and llvm-readelf.
build 7119240
Reduce prepare recording time.
Add --kprobe option in record cmd.
Add --cpu option in report cmd.
Add -i option in dump cmd.
Add --exclude-perf option in inject cmd.
Add merge cmd to merge recording files recorded in the same environment using
the same event types.
Add monitor cmd to record and report events in real time.
Fix a few bugs about symbolization of kernel and kernel modules.
Support parsing kernel etm data in inject cmd.
Add --show-execution-type option in report-sample cmd.
Don't hide art jni methods in report_lib and report-sample cmd.
build 6859468
Add --csv option in report cmd.
Add --sort option in stat cmd.
Add --tp-filter option to filter tracepoint events in record cmd.
Add --addr-filter to filter etm recording in record cmd.
Fix finding symbols from kernel modules.
Better ART JIT support (dump jit symfiles to a single file instead of multiple
temporary files).
Support generic JIT symbols from symbol map file. See doc/jit_symbols.md.
ndk r22
build 6401870
Support multiple record files in pprof_proto_generator.py.
In stat cmd, add --per-thread and --per-core options to report per thread and per core.
In record cmd, add --exclude-perf option to exclude simpleperf samples in system wide
recording.
In inject cmd, support decoding coresight etm data to branch list data in protobuf format.
Fix and add doc for app_api, which can control simpleperf recording in app code.
Support pmu event types:
list supported pmu events via `simpleperf list pmu`.
record/stat pmu events via options like -e armv8_pmuv3/cpu_cycles/.
Switch to llvm-objdump.
Add doc for line and disassembly annotation in README.md.
Add doc for profiling profileable release app on Android >= Q.
Remove dependency on libncurses.
ndk r21
In record cmd, support recording coresight etm data (via -e cs-etm option).
Add inject cmd to decode coresight etm data.
Add doc for downloading unstripped libraries on device.
Fix scripts for using unstripped libraries without build ids for reporting.
Switch to llvm-symbolizer.
Add app_api and api_profiler.py, which can control simpleperf recording in app code.
Fix pprof_proto_generator.py to support line and disassembly annotation via pprof.
ndk r20
Skipped.
ndk r19
Fix report-sample command on Windows.
ndk r18
Improve support of profiling JITed/interpreted Java code on Android >= P:
1) Support JITed/interpreted Java code in system wide recording.
2) Support dex files extracted to memory.
3) Fix some bugs and improve inefficient code.
Improve record command:
1) Add a user space buffer and a high priority record reading thread to reduce sampe lost rate.
2) Record full process name instead of only the last 16 bytes.
Improve report_html.py:
1) Generate flamegraphs in Javascript code instead of using inferno, thus
reducing the time used to generate and load report.
2) Use bootstrap 4 to format UI.
3) Use progressbar to show progress of loading contents.
4) Add --binary_filter option to only annotate selected binaries.
Export tracing data in simpleperf_report_lib.py.
Test python scripts with both python2 and python3.
Add document for using simpleperf in Android platform profiling.
ndk r17
(release)
Use new Android unwinder, which can unwind for archs different from build.
Support profiling interpreted and JITed java code on Android >= P.
Refactor app_profiler.py: improve option interface, simplify profiling from launch,
and improve native lib downloading.
Fix ndk issues 638, 644, 499, 493.
Add debug-unwind cmd and script to debug unwinding.
Update document, including the way using wrap.sh to profile released apk.
(beta 1)
Add report_html.py, reporting profiling result in html interface.
Improve inferno.
Refactor document.
Provide more complete dwarf based call graphs.
ndk r16
Add inferno, a flamegraph generator.
Add --trace-offcpu option in simpleperf record command and app_profiler.py to trace offcpu time.
Add --app option in simpleperf record command to remove need of using run-as.
Add --profile_from_launch option in app_profiler.py to start recording from Activity launch time.
Configure scripts from command lines, remove config files.
Wrap simpleperf report command with report.py, in which GUI mode is enabled with --gui option.
Add release tests for scripts.
ndk r15
Add three Android Studio project examples, show how to build optimized native libs containing
debug info, show how to fully compile app on Android O.
Add symbol info in perf.data by default, no need to add --dump-symbols in simpleperf record command.
Report brief call-graph in simpleperf report command.
Support raw cpu pmu events.
ndk r14
Add app_profiler.py to help recording profiling data.
Add annotate.py to annotate source code.
Add simpleperf_report_lib.py interface to support extracting samples from perf.data.
Release simpleperf binaries on host to support reporting on host.
ndk r13
Release simpleperf binaries on device.
Support recording and reporting stack frame based callgraphs and dwarf based callgraphs.
Add simpleperf_report.py to show callgraphs in GUI.

View File

@ -0,0 +1,486 @@
#!/usr/bin/env python3
#
# Copyright (C) 2016 The Android Open Source Project
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
"""annotate.py: annotate source files based on perf.data.
"""
import logging
import os
import os.path
import shutil
from texttable import Texttable
from typing import Dict, Union
from simpleperf_report_lib import GetReportLib
from simpleperf_utils import (
Addr2Nearestline, BaseArgumentParser, BinaryFinder, extant_dir, flatten_arg_list, is_windows,
log_exit, ReadElf, SourceFileSearcher)
class SourceLine(object):
def __init__(self, file_id, function, line):
self.file = file_id
self.function = function
self.line = line
@property
def file_key(self):
return self.file
@property
def function_key(self):
return (self.file, self.function)
@property
def line_key(self):
return (self.file, self.line)
class Addr2Line(object):
"""collect information of how to map [dso_name, vaddr] to [source_file:line].
"""
def __init__(self, ndk_path, binary_cache_path, source_dirs):
binary_finder = BinaryFinder(binary_cache_path, ReadElf(ndk_path))
self.addr2line = Addr2Nearestline(ndk_path, binary_finder, True)
self.source_searcher = SourceFileSearcher(source_dirs)
def add_addr(self, dso_path: str, build_id: str, func_addr: int, addr: int):
self.addr2line.add_addr(dso_path, build_id, func_addr, addr)
def convert_addrs_to_lines(self):
self.addr2line.convert_addrs_to_lines(jobs=os.cpu_count())
def get_sources(self, dso_path, addr):
dso = self.addr2line.get_dso(dso_path)
if not dso:
return []
source = self.addr2line.get_addr_source(dso, addr)
if not source:
return []
result = []
for (source_file, source_line, function_name) in source:
source_file_path = self.source_searcher.get_real_path(source_file)
if not source_file_path:
source_file_path = source_file
result.append(SourceLine(source_file_path, function_name, source_line))
return result
class Period(object):
"""event count information. It can be used to represent event count
of a line, a function, a source file, or a binary. It contains two
parts: period and acc_period.
When used for a line, period is the event count occurred when running
that line, acc_period is the accumulated event count occurred when
running that line and functions called by that line. Same thing applies
when it is used for a function, a source file, or a binary.
"""
def __init__(self, period=0, acc_period=0):
self.period = period
self.acc_period = acc_period
def __iadd__(self, other):
self.period += other.period
self.acc_period += other.acc_period
return self
class DsoPeriod(object):
"""Period for each shared library"""
def __init__(self, dso_name):
self.dso_name = dso_name
self.period = Period()
def add_period(self, period):
self.period += period
class FilePeriod(object):
"""Period for each source file"""
def __init__(self, file_id):
self.file = file_id
self.period = Period()
# Period for each line in the file.
self.line_dict = {}
# Period for each function in the source file.
self.function_dict = {}
def add_period(self, period):
self.period += period
def add_line_period(self, line, period):
a = self.line_dict.get(line)
if a is None:
self.line_dict[line] = a = Period()
a += period
def add_function_period(self, function_name, function_start_line, period):
a = self.function_dict.get(function_name)
if not a:
if function_start_line is None:
function_start_line = -1
self.function_dict[function_name] = a = [function_start_line, Period()]
a[1] += period
class SourceFileAnnotator(object):
"""group code for annotating source files"""
def __init__(self, config):
# check config variables
config_names = ['perf_data_list', 'source_dirs', 'dso_filters', 'ndk_path']
for name in config_names:
if name not in config:
log_exit('config [%s] is missing' % name)
symfs_dir = 'binary_cache'
if not os.path.isdir(symfs_dir):
symfs_dir = None
kallsyms = 'binary_cache/kallsyms'
if not os.path.isfile(kallsyms):
kallsyms = None
# init member variables
self.config = config
self.symfs_dir = symfs_dir
self.kallsyms = kallsyms
self.dso_filter = set(config['dso_filters']) if config.get('dso_filters') else None
config['annotate_dest_dir'] = 'annotated_files'
output_dir = config['annotate_dest_dir']
if os.path.isdir(output_dir):
shutil.rmtree(output_dir)
os.makedirs(output_dir)
self.addr2line = Addr2Line(self.config['ndk_path'], symfs_dir, config.get('source_dirs'))
self.period = 0
self.dso_periods = {}
self.file_periods = {}
def annotate(self):
self._collect_addrs()
self._convert_addrs_to_lines()
self._generate_periods()
self._write_summary()
self._annotate_files()
def _collect_addrs(self):
"""Read perf.data, collect all addresses we need to convert to
source file:line.
"""
for perf_data in self.config['perf_data_list']:
lib = GetReportLib(perf_data)
if self.symfs_dir:
lib.SetSymfs(self.symfs_dir)
if self.kallsyms:
lib.SetKallsymsFile(self.kallsyms)
lib.SetReportOptions(self.config['report_lib_options'])
while True:
sample = lib.GetNextSample()
if sample is None:
lib.Close()
break
symbols = []
symbols.append(lib.GetSymbolOfCurrentSample())
callchain = lib.GetCallChainOfCurrentSample()
for i in range(callchain.nr):
symbols.append(callchain.entries[i].symbol)
for symbol in symbols:
if self._filter_symbol(symbol):
build_id = lib.GetBuildIdForPath(symbol.dso_name)
self.addr2line.add_addr(symbol.dso_name, build_id, symbol.symbol_addr,
symbol.vaddr_in_file)
self.addr2line.add_addr(symbol.dso_name, build_id, symbol.symbol_addr,
symbol.symbol_addr)
def _filter_symbol(self, symbol):
if not self.dso_filter or symbol.dso_name in self.dso_filter:
return True
return False
def _convert_addrs_to_lines(self):
self.addr2line.convert_addrs_to_lines()
def _generate_periods(self):
"""read perf.data, collect Period for all types:
binaries, source files, functions, lines.
"""
for perf_data in self.config['perf_data_list']:
lib = GetReportLib(perf_data)
if self.symfs_dir:
lib.SetSymfs(self.symfs_dir)
if self.kallsyms:
lib.SetKallsymsFile(self.kallsyms)
lib.SetReportOptions(self.config['report_lib_options'])
while True:
sample = lib.GetNextSample()
if sample is None:
lib.Close()
break
self._generate_periods_for_sample(lib, sample)
def _generate_periods_for_sample(self, lib, sample):
symbols = []
symbols.append(lib.GetSymbolOfCurrentSample())
callchain = lib.GetCallChainOfCurrentSample()
for i in range(callchain.nr):
symbols.append(callchain.entries[i].symbol)
# Each sample has a callchain, but its period is only used once
# to add period for each function/source_line/source_file/binary.
# For example, if more than one entry in the callchain hits a
# function, the event count of that function is only increased once.
# Otherwise, we may get periods > 100%.
is_sample_used = False
used_dso_dict = {}
used_file_dict = {}
used_function_dict = {}
used_line_dict = {}
period = Period(sample.period, sample.period)
for j, symbol in enumerate(symbols):
if j == 1:
period = Period(0, sample.period)
if not self._filter_symbol(symbol):
continue
is_sample_used = True
# Add period to dso.
self._add_dso_period(symbol.dso_name, period, used_dso_dict)
# Add period to source file.
sources = self.addr2line.get_sources(symbol.dso_name, symbol.vaddr_in_file)
for source in sources:
if source.file:
self._add_file_period(source, period, used_file_dict)
# Add period to line.
if source.line:
self._add_line_period(source, period, used_line_dict)
# Add period to function.
sources = self.addr2line.get_sources(symbol.dso_name, symbol.symbol_addr)
for source in sources:
if source.file:
self._add_file_period(source, period, used_file_dict)
if source.function:
self._add_function_period(source, period, used_function_dict)
if is_sample_used:
self.period += sample.period
def _add_dso_period(self, dso_name: str, period: Period, used_dso_dict: Dict[str, bool]):
if dso_name not in used_dso_dict:
used_dso_dict[dso_name] = True
dso_period = self.dso_periods.get(dso_name)
if dso_period is None:
dso_period = self.dso_periods[dso_name] = DsoPeriod(dso_name)
dso_period.add_period(period)
def _add_file_period(self, source, period, used_file_dict):
if source.file_key not in used_file_dict:
used_file_dict[source.file_key] = True
file_period = self.file_periods.get(source.file)
if file_period is None:
file_period = self.file_periods[source.file] = FilePeriod(source.file)
file_period.add_period(period)
def _add_line_period(self, source, period, used_line_dict):
if source.line_key not in used_line_dict:
used_line_dict[source.line_key] = True
file_period = self.file_periods[source.file]
file_period.add_line_period(source.line, period)
def _add_function_period(self, source, period, used_function_dict):
if source.function_key not in used_function_dict:
used_function_dict[source.function_key] = True
file_period = self.file_periods[source.file]
file_period.add_function_period(source.function, source.line, period)
def _write_summary(self):
summary = os.path.join(self.config['annotate_dest_dir'], 'summary')
with open(summary, 'w') as f:
f.write('total period: %d\n\n' % self.period)
self._write_dso_summary(f)
self._write_file_summary(f)
file_periods = sorted(self.file_periods.values(),
key=lambda x: x.period.acc_period, reverse=True)
for file_period in file_periods:
self._write_function_line_summary(f, file_period)
def _write_dso_summary(self, summary_fh):
dso_periods = sorted(self.dso_periods.values(),
key=lambda x: x.period.acc_period, reverse=True)
table = Texttable(max_width=self.config['summary_width'])
table.set_cols_align(['l', 'l', 'l'])
table.add_row(['Total', 'Self', 'DSO'])
for dso_period in dso_periods:
total_str = self._get_period_str(dso_period.period.acc_period)
self_str = self._get_period_str(dso_period.period.period)
table.add_row([total_str, self_str, dso_period.dso_name])
print(table.draw(), file=summary_fh)
print(file=summary_fh)
def _write_file_summary(self, summary_fh):
file_periods = sorted(self.file_periods.values(),
key=lambda x: x.period.acc_period, reverse=True)
table = Texttable(max_width=self.config['summary_width'])
table.set_cols_align(['l', 'l', 'l'])
table.add_row(['Total', 'Self', 'Source File'])
for file_period in file_periods:
total_str = self._get_period_str(file_period.period.acc_period)
self_str = self._get_period_str(file_period.period.period)
table.add_row([total_str, self_str, file_period.file])
print(table.draw(), file=summary_fh)
print(file=summary_fh)
def _write_function_line_summary(self, summary_fh, file_period: FilePeriod):
table = Texttable(max_width=self.config['summary_width'])
table.set_cols_align(['l', 'l', 'l'])
table.add_row(['Total', 'Self', 'Function/Line in ' + file_period.file])
values = []
for func_name in file_period.function_dict.keys():
func_start_line, period = file_period.function_dict[func_name]
values.append((func_name, func_start_line, period))
values.sort(key=lambda x: x[2].acc_period, reverse=True)
for func_name, func_start_line, period in values:
total_str = self._get_period_str(period.acc_period)
self_str = self._get_period_str(period.period)
name = func_name + ' (line %d)' % func_start_line
table.add_row([total_str, self_str, name])
for line in sorted(file_period.line_dict.keys()):
period = file_period.line_dict[line]
total_str = self._get_period_str(period.acc_period)
self_str = self._get_period_str(period.period)
name = 'line %d' % line
table.add_row([total_str, self_str, name])
print(table.draw(), file=summary_fh)
print(file=summary_fh)
def _get_period_str(self, period: Union[Period, int]) -> str:
if isinstance(period, Period):
return 'Total %s, Self %s' % (
self._get_period_str(period.acc_period),
self._get_period_str(period.period))
if self.config['raw_period'] or self.period == 0:
return str(period)
return '%.2f%%' % (100.0 * period / self.period)
def _annotate_files(self):
"""Annotate Source files: add acc_period/period for each source file.
1. Annotate java source files, which have $JAVA_SRC_ROOT prefix.
2. Annotate c++ source files.
"""
dest_dir = self.config['annotate_dest_dir']
for key in self.file_periods:
from_path = key
if not os.path.isfile(from_path):
logging.warning("can't find source file for path %s" % from_path)
continue
if from_path.startswith('/'):
to_path = os.path.join(dest_dir, from_path[1:])
elif is_windows() and ':\\' in from_path:
to_path = os.path.join(dest_dir, from_path.replace(':\\', os.sep))
else:
to_path = os.path.join(dest_dir, from_path)
is_java = from_path.endswith('.java')
self._annotate_file(from_path, to_path, self.file_periods[key], is_java)
def _annotate_file(self, from_path, to_path, file_period, is_java):
"""Annotate a source file.
Annotate a source file in three steps:
1. In the first line, show periods of this file.
2. For each function, show periods of this function.
3. For each line not hitting the same line as functions, show
line periods.
"""
logging.info('annotate file %s' % from_path)
with open(from_path, 'r') as rf:
lines = rf.readlines()
annotates = {}
for line in file_period.line_dict.keys():
annotates[line] = self._get_period_str(file_period.line_dict[line])
for func_name in file_period.function_dict.keys():
func_start_line, period = file_period.function_dict[func_name]
if func_start_line == -1:
continue
line = func_start_line - 1 if is_java else func_start_line
annotates[line] = '[func] ' + self._get_period_str(period)
annotates[1] = '[file] ' + self._get_period_str(file_period.period)
max_annotate_cols = 0
for key in annotates:
max_annotate_cols = max(max_annotate_cols, len(annotates[key]))
empty_annotate = ' ' * (max_annotate_cols + 6)
dirname = os.path.dirname(to_path)
if not os.path.isdir(dirname):
os.makedirs(dirname)
with open(to_path, 'w') as wf:
for line in range(1, len(lines) + 1):
annotate = annotates.get(line)
if annotate is None:
if not lines[line-1].strip():
annotate = ''
else:
annotate = empty_annotate
else:
annotate = '/* ' + annotate + (
' ' * (max_annotate_cols - len(annotate))) + ' */'
wf.write(annotate)
wf.write(lines[line-1])
def main():
parser = BaseArgumentParser(description="""
Annotate source files based on profiling data. It reads line information from binary_cache
generated by app_profiler.py or binary_cache_builder.py, and generate annotated source
files in annotated_files directory.""")
parser.add_argument('-i', '--perf_data_list', nargs='+', action='append', help="""
The paths of profiling data. Default is perf.data.""")
parser.add_argument('-s', '--source_dirs', type=extant_dir, nargs='+', action='append', help="""
Directories to find source files.""")
parser.add_argument('--ndk_path', type=extant_dir, help='Set the path of a ndk release.')
parser.add_argument('--raw-period', action='store_true',
help='show raw period instead of percentage')
parser.add_argument('--summary-width', type=int, default=80, help='max width of summary file')
sample_filter_group = parser.add_argument_group('Sample filter options')
sample_filter_group.add_argument('--dso', nargs='+', action='append', help="""
Use samples only in selected binaries.""")
parser.add_report_lib_options(sample_filter_group=sample_filter_group)
args = parser.parse_args()
config = {}
config['perf_data_list'] = flatten_arg_list(args.perf_data_list)
if not config['perf_data_list']:
config['perf_data_list'].append('perf.data')
config['source_dirs'] = flatten_arg_list(args.source_dirs)
config['dso_filters'] = flatten_arg_list(args.dso)
config['ndk_path'] = args.ndk_path
config['raw_period'] = args.raw_period
config['summary_width'] = args.summary_width
config['report_lib_options'] = args.report_lib_options
annotator = SourceFileAnnotator(config)
annotator.annotate()
logging.info('annotate finish successfully, please check result in annotated_files/.')
if __name__ == '__main__':
main()

View File

@ -0,0 +1,131 @@
#!/usr/bin/env python3
#
# Copyright (C) 2019 The Android Open Source Project
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
"""
This script is part of controlling simpleperf recording in user code. It is used to prepare
profiling environment (upload simpleperf to device and enable profiling) before recording
and collect recording data on host after recording.
Controlling simpleperf recording is done in below steps:
1. Add simpleperf Java API/C++ API to the app's source code. And call the API in user code.
2. Run `api_profiler.py prepare` to prepare profiling environment.
3. Run the app one or more times to generate recording data.
4. Run `api_profiler.py collect` to collect recording data on host.
"""
from argparse import Namespace
import logging
import os
import os.path
import shutil
import zipfile
from simpleperf_utils import (AdbHelper, BaseArgumentParser,
get_target_binary_path, log_exit, remove)
class ApiProfiler:
def __init__(self, args: Namespace):
self.args = args
self.adb = AdbHelper()
def prepare_recording(self):
self.enable_profiling_on_device()
self.upload_simpleperf_to_device()
self.run_simpleperf_prepare_cmd()
def enable_profiling_on_device(self):
android_version = self.adb.get_android_version()
if android_version >= 10:
self.adb.set_property('debug.perf_event_max_sample_rate',
str(self.args.max_sample_rate))
self.adb.set_property('debug.perf_cpu_time_max_percent', str(self.args.max_cpu_percent))
self.adb.set_property('debug.perf_event_mlock_kb', str(self.args.max_memory_in_kb))
self.adb.set_property('security.perf_harden', '0')
def upload_simpleperf_to_device(self):
device_arch = self.adb.get_device_arch()
simpleperf_binary = get_target_binary_path(device_arch, 'simpleperf')
self.adb.check_run(['push', simpleperf_binary, '/data/local/tmp'])
self.adb.check_run(['shell', 'chmod', 'a+x', '/data/local/tmp/simpleperf'])
def run_simpleperf_prepare_cmd(self):
cmd_args = ['shell', '/data/local/tmp/simpleperf', 'api-prepare', '--app', self.args.app]
if self.args.days:
cmd_args += ['--days', str(self.args.days)]
self.adb.check_run(cmd_args)
def collect_data(self):
if not os.path.isdir(self.args.out_dir):
os.makedirs(self.args.out_dir)
self.download_recording_data()
self.unzip_recording_data()
def download_recording_data(self):
""" download recording data to simpleperf_data.zip."""
self.upload_simpleperf_to_device()
self.adb.check_run(['shell', '/data/local/tmp/simpleperf', 'api-collect',
'--app', self.args.app, '-o', '/data/local/tmp/simpleperf_data.zip'])
self.adb.check_run(['pull', '/data/local/tmp/simpleperf_data.zip', self.args.out_dir])
self.adb.check_run(['shell', 'rm', '-rf', '/data/local/tmp/simpleperf_data'])
def unzip_recording_data(self):
zip_file_path = os.path.join(self.args.out_dir, 'simpleperf_data.zip')
with zipfile.ZipFile(zip_file_path, 'r') as zip_fh:
names = zip_fh.namelist()
logging.info('There are %d recording data files.' % len(names))
for name in names:
logging.info('recording file: %s' % os.path.join(self.args.out_dir, name))
zip_fh.extract(name, self.args.out_dir)
remove(zip_file_path)
def main():
parser = BaseArgumentParser(description=__doc__)
subparsers = parser.add_subparsers(title='actions', dest='command')
prepare_parser = subparsers.add_parser('prepare', help='Prepare recording on device.')
prepare_parser.add_argument('-p', '--app', required=True, help="""
The app package name of the app profiled.""")
prepare_parser.add_argument('-d', '--days', type=int, help="""
By default, the recording permission is reset after device reboot.
But on Android >= 13, we can use --days to set how long we want the
permission to persist. It can last after device reboot.
""")
prepare_parser.add_argument('--max-sample-rate', type=int, default=100000, help="""
Set max sample rate (only on Android >= Q).""")
prepare_parser.add_argument('--max-cpu-percent', type=int, default=25, help="""
Set max cpu percent for recording (only on Android >= Q).""")
prepare_parser.add_argument('--max-memory-in-kb', type=int,
default=(1024 + 1) * 4 * 8, help="""
Set max kernel buffer size for recording (only on Android >= Q).
""")
collect_parser = subparsers.add_parser('collect', help='Collect recording data.')
collect_parser.add_argument('-p', '--app', required=True, help="""
The app package name of the app profiled.""")
collect_parser.add_argument('-o', '--out-dir', default='simpleperf_data', help="""
The directory to store recording data.""")
args = parser.parse_args()
if args.command == 'prepare':
ApiProfiler(args).prepare_recording()
elif args.command == 'collect':
ApiProfiler(args).collect_data()
if __name__ == '__main__':
main()

View File

@ -0,0 +1,530 @@
/*
* Copyright (C) 2019 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "simpleperf.h"
#include <limits.h>
#include <stdarg.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <time.h>
#include <unistd.h>
#include <android/log.h>
#include <mutex>
#include <sstream>
namespace simpleperf {
constexpr int AID_USER_OFFSET = 100000;
enum RecordCmd {
CMD_PAUSE_RECORDING = 1,
CMD_RESUME_RECORDING,
};
class RecordOptionsImpl {
public:
std::string output_filename;
std::string event = "cpu-cycles";
size_t freq = 4000;
double duration_in_second = 0.0;
std::vector<pid_t> threads;
bool dwarf_callgraph = false;
bool fp_callgraph = false;
bool trace_offcpu = false;
};
RecordOptions::RecordOptions() : impl_(new RecordOptionsImpl) {}
RecordOptions::~RecordOptions() {
delete impl_;
}
RecordOptions& RecordOptions::SetOutputFilename(const std::string& filename) {
impl_->output_filename = filename;
return *this;
}
RecordOptions& RecordOptions::SetEvent(const std::string& event) {
impl_->event = event;
return *this;
}
RecordOptions& RecordOptions::SetSampleFrequency(size_t freq) {
impl_->freq = freq;
return *this;
}
RecordOptions& RecordOptions::SetDuration(double duration_in_second) {
impl_->duration_in_second = duration_in_second;
return *this;
}
RecordOptions& RecordOptions::SetSampleThreads(const std::vector<pid_t>& threads) {
impl_->threads = threads;
return *this;
}
RecordOptions& RecordOptions::RecordDwarfCallGraph() {
impl_->dwarf_callgraph = true;
impl_->fp_callgraph = false;
return *this;
}
RecordOptions& RecordOptions::RecordFramePointerCallGraph() {
impl_->fp_callgraph = true;
impl_->dwarf_callgraph = false;
return *this;
}
RecordOptions& RecordOptions::TraceOffCpu() {
impl_->trace_offcpu = true;
return *this;
}
static std::string GetDefaultOutputFilename() {
time_t t = time(nullptr);
struct tm tm;
if (localtime_r(&t, &tm) != &tm) {
return "perf.data";
}
char* buf = nullptr;
asprintf(&buf, "perf-%02d-%02d-%02d-%02d-%02d.data", tm.tm_mon + 1, tm.tm_mday, tm.tm_hour,
tm.tm_min, tm.tm_sec);
std::string result = buf;
free(buf);
return result;
}
std::vector<std::string> RecordOptions::ToRecordArgs() const {
std::vector<std::string> args;
std::string output_filename = impl_->output_filename;
if (output_filename.empty()) {
output_filename = GetDefaultOutputFilename();
}
args.insert(args.end(), {"-o", output_filename});
args.insert(args.end(), {"-e", impl_->event});
args.insert(args.end(), {"-f", std::to_string(impl_->freq)});
if (impl_->duration_in_second != 0.0) {
args.insert(args.end(), {"--duration", std::to_string(impl_->duration_in_second)});
}
if (impl_->threads.empty()) {
args.insert(args.end(), {"-p", std::to_string(getpid())});
} else {
std::ostringstream os;
os << *(impl_->threads.begin());
for (auto it = std::next(impl_->threads.begin()); it != impl_->threads.end(); ++it) {
os << "," << *it;
}
args.insert(args.end(), {"-t", os.str()});
}
if (impl_->dwarf_callgraph) {
args.push_back("-g");
} else if (impl_->fp_callgraph) {
args.insert(args.end(), {"--call-graph", "fp"});
}
if (impl_->trace_offcpu) {
args.push_back("--trace-offcpu");
}
return args;
}
static void Abort(const char* fmt, ...) {
va_list vl;
va_start(vl, fmt);
__android_log_vprint(ANDROID_LOG_FATAL, "simpleperf", fmt, vl);
va_end(vl);
abort();
}
class ProfileSessionImpl {
public:
ProfileSessionImpl(const std::string& app_data_dir)
: app_data_dir_(app_data_dir), simpleperf_data_dir_(app_data_dir + "/simpleperf_data") {}
~ProfileSessionImpl();
void StartRecording(const std::vector<std::string>& args);
void PauseRecording();
void ResumeRecording();
void StopRecording();
private:
std::string FindSimpleperf();
std::string FindSimpleperfInTempDir();
void CheckIfPerfEnabled();
std::string GetProperty(const std::string& name);
void CreateSimpleperfDataDir();
void CreateSimpleperfProcess(const std::string& simpleperf_path,
const std::vector<std::string>& record_args);
void SendCmd(const std::string& cmd);
std::string ReadReply();
enum State {
NOT_YET_STARTED,
STARTED,
PAUSED,
STOPPED,
};
const std::string app_data_dir_;
const std::string simpleperf_data_dir_;
std::mutex lock_; // Protect all members below.
State state_ = NOT_YET_STARTED;
pid_t simpleperf_pid_ = -1;
int control_fd_ = -1;
int reply_fd_ = -1;
bool trace_offcpu_ = false;
};
ProfileSessionImpl::~ProfileSessionImpl() {
if (control_fd_ != -1) {
close(control_fd_);
}
if (reply_fd_ != -1) {
close(reply_fd_);
}
}
void ProfileSessionImpl::StartRecording(const std::vector<std::string>& args) {
std::lock_guard<std::mutex> guard(lock_);
if (state_ != NOT_YET_STARTED) {
Abort("startRecording: session in wrong state %d", state_);
}
for (const auto& arg : args) {
if (arg == "--trace-offcpu") {
trace_offcpu_ = true;
}
}
std::string simpleperf_path = FindSimpleperf();
CheckIfPerfEnabled();
CreateSimpleperfDataDir();
CreateSimpleperfProcess(simpleperf_path, args);
state_ = STARTED;
}
void ProfileSessionImpl::PauseRecording() {
std::lock_guard<std::mutex> guard(lock_);
if (state_ != STARTED) {
Abort("pauseRecording: session in wrong state %d", state_);
}
if (trace_offcpu_) {
Abort("--trace-offcpu doesn't work well with pause/resume recording");
}
SendCmd("pause");
state_ = PAUSED;
}
void ProfileSessionImpl::ResumeRecording() {
std::lock_guard<std::mutex> guard(lock_);
if (state_ != PAUSED) {
Abort("resumeRecording: session in wrong state %d", state_);
}
SendCmd("resume");
state_ = STARTED;
}
void ProfileSessionImpl::StopRecording() {
std::lock_guard<std::mutex> guard(lock_);
if (state_ != STARTED && state_ != PAUSED) {
Abort("stopRecording: session in wrong state %d", state_);
}
// Send SIGINT to simpleperf to stop recording.
if (kill(simpleperf_pid_, SIGINT) == -1) {
Abort("failed to stop simpleperf: %s", strerror(errno));
}
int status;
pid_t result = TEMP_FAILURE_RETRY(waitpid(simpleperf_pid_, &status, 0));
if (result == -1) {
Abort("failed to call waitpid: %s", strerror(errno));
}
if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) {
Abort("simpleperf exited with error, status = 0x%x", status);
}
state_ = STOPPED;
}
void ProfileSessionImpl::SendCmd(const std::string& cmd) {
std::string data = cmd + "\n";
if (TEMP_FAILURE_RETRY(write(control_fd_, &data[0], data.size())) !=
static_cast<ssize_t>(data.size())) {
Abort("failed to send cmd to simpleperf: %s", strerror(errno));
}
if (ReadReply() != "ok") {
Abort("failed to run cmd in simpleperf: %s", cmd.c_str());
}
}
static bool IsExecutableFile(const std::string& path) {
struct stat st;
if (stat(path.c_str(), &st) == 0) {
if (S_ISREG(st.st_mode) && (st.st_mode & S_IXUSR)) {
return true;
}
}
return false;
}
static std::string ReadFile(FILE* fp) {
std::string s;
if (fp == nullptr) {
return s;
}
char buf[200];
while (true) {
ssize_t n = fread(buf, 1, sizeof(buf), fp);
if (n <= 0) {
break;
}
s.insert(s.end(), buf, buf + n);
}
fclose(fp);
return s;
}
static bool RunCmd(std::vector<const char*> args, std::string* stdout) {
int stdout_fd[2];
if (pipe(stdout_fd) != 0) {
return false;
}
args.push_back(nullptr);
// Fork handlers (like gsl_library_close) may hang in a multi-thread environment.
// So we use vfork instead of fork to avoid calling them.
int pid = vfork();
if (pid == -1) {
return false;
}
if (pid == 0) {
// child process
close(stdout_fd[0]);
dup2(stdout_fd[1], 1);
close(stdout_fd[1]);
execvp(const_cast<char*>(args[0]), const_cast<char**>(args.data()));
_exit(1);
}
// parent process
close(stdout_fd[1]);
int status;
pid_t result = TEMP_FAILURE_RETRY(waitpid(pid, &status, 0));
if (result == -1) {
Abort("failed to call waitpid: %s", strerror(errno));
}
if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) {
return false;
}
if (stdout == nullptr) {
close(stdout_fd[0]);
} else {
*stdout = ReadFile(fdopen(stdout_fd[0], "r"));
}
return true;
}
std::string ProfileSessionImpl::FindSimpleperf() {
// 1. Try /data/local/tmp/simpleperf first. Probably it's newer than /system/bin/simpleperf.
std::string simpleperf_path = FindSimpleperfInTempDir();
if (!simpleperf_path.empty()) {
return simpleperf_path;
}
// 2. Try /system/bin/simpleperf, which is available on Android >= Q.
simpleperf_path = "/system/bin/simpleperf";
if (IsExecutableFile(simpleperf_path)) {
return simpleperf_path;
}
Abort("can't find simpleperf on device. Please run api_profiler.py.");
return "";
}
std::string ProfileSessionImpl::FindSimpleperfInTempDir() {
const std::string path = "/data/local/tmp/simpleperf";
if (!IsExecutableFile(path)) {
return "";
}
// Copy it to app_dir to execute it.
const std::string to_path = app_data_dir_ + "/simpleperf";
if (!RunCmd({"/system/bin/cp", path.c_str(), to_path.c_str()}, nullptr)) {
return "";
}
// For apps with target sdk >= 29, executing app data file isn't allowed.
// For android R, app context isn't allowed to use perf_event_open.
// So test executing downloaded simpleperf.
std::string s;
if (!RunCmd({to_path.c_str(), "list", "sw"}, &s)) {
return "";
}
if (s.find("cpu-clock") == std::string::npos) {
return "";
}
return to_path;
}
void ProfileSessionImpl::CheckIfPerfEnabled() {
if (GetProperty("persist.simpleperf.profile_app_uid") == std::to_string(getuid())) {
std::string time_str = GetProperty("persist.simpleperf.profile_app_expiration_time");
if (!time_str.empty()) {
errno = 0;
uint64_t expiration_time = strtoull(time_str.data(), nullptr, 10);
if (errno == 0 && expiration_time > time(nullptr)) {
return;
}
}
}
if (GetProperty("security.perf_harden") == "1") {
Abort("Recording app isn't enabled on the device. Please run api_profiler.py.");
}
}
std::string ProfileSessionImpl::GetProperty(const std::string& name) {
std::string s;
if (!RunCmd({"/system/bin/getprop", name.c_str()}, &s)) {
return "";
}
return s;
}
void ProfileSessionImpl::CreateSimpleperfDataDir() {
struct stat st;
if (stat(simpleperf_data_dir_.c_str(), &st) == 0 && S_ISDIR(st.st_mode)) {
return;
}
if (mkdir(simpleperf_data_dir_.c_str(), 0700) == -1) {
Abort("failed to create simpleperf data dir %s: %s", simpleperf_data_dir_.c_str(),
strerror(errno));
}
}
void ProfileSessionImpl::CreateSimpleperfProcess(const std::string& simpleperf_path,
const std::vector<std::string>& record_args) {
// 1. Create control/reply pips.
int control_fd[2];
int reply_fd[2];
if (pipe(control_fd) != 0 || pipe(reply_fd) != 0) {
Abort("failed to call pipe: %s", strerror(errno));
}
// 2. Prepare simpleperf arguments.
std::vector<std::string> args;
args.emplace_back(simpleperf_path);
args.emplace_back("record");
args.emplace_back("--log-to-android-buffer");
args.insert(args.end(), {"--log", "debug"});
args.emplace_back("--stdio-controls-profiling");
args.emplace_back("--in-app");
args.insert(args.end(), {"--tracepoint-events", "/data/local/tmp/tracepoint_events"});
args.insert(args.end(), record_args.begin(), record_args.end());
char* argv[args.size() + 1];
for (size_t i = 0; i < args.size(); ++i) {
argv[i] = &args[i][0];
}
argv[args.size()] = nullptr;
// 3. Start simpleperf process.
// Fork handlers (like gsl_library_close) may hang in a multi-thread environment.
// So we use vfork instead of fork to avoid calling them.
int pid = vfork();
if (pid == -1) {
Abort("failed to fork: %s", strerror(errno));
}
if (pid == 0) {
// child process
close(control_fd[1]);
dup2(control_fd[0], 0); // simpleperf read control cmd from fd 0.
close(control_fd[0]);
close(reply_fd[0]);
dup2(reply_fd[1], 1); // simpleperf writes reply to fd 1.
close(reply_fd[0]);
chdir(simpleperf_data_dir_.c_str());
execvp(argv[0], argv);
Abort("failed to call exec: %s", strerror(errno));
}
// parent process
close(control_fd[0]);
control_fd_ = control_fd[1];
close(reply_fd[1]);
reply_fd_ = reply_fd[0];
simpleperf_pid_ = pid;
// 4. Wait until simpleperf starts recording.
std::string start_flag = ReadReply();
if (start_flag != "started") {
Abort("failed to receive simpleperf start flag");
}
}
std::string ProfileSessionImpl::ReadReply() {
std::string s;
while (true) {
char c;
ssize_t result = TEMP_FAILURE_RETRY(read(reply_fd_, &c, 1));
if (result <= 0 || c == '\n') {
break;
}
s.push_back(c);
}
return s;
}
ProfileSession::ProfileSession() {
FILE* fp = fopen("/proc/self/cmdline", "r");
if (fp == nullptr) {
Abort("failed to open /proc/self/cmdline: %s", strerror(errno));
}
std::string s = ReadFile(fp);
for (int i = 0; i < s.size(); i++) {
if (s[i] == '\0') {
s = s.substr(0, i);
break;
}
}
std::string app_data_dir = "/data/data/" + s;
int uid = getuid();
if (uid >= AID_USER_OFFSET) {
int user_id = uid / AID_USER_OFFSET;
app_data_dir = "/data/user/" + std::to_string(user_id) + "/" + s;
}
impl_ = new ProfileSessionImpl(app_data_dir);
}
ProfileSession::ProfileSession(const std::string& app_data_dir)
: impl_(new ProfileSessionImpl(app_data_dir)) {}
ProfileSession::~ProfileSession() {
delete impl_;
}
void ProfileSession::StartRecording(const RecordOptions& options) {
StartRecording(options.ToRecordArgs());
}
void ProfileSession::StartRecording(const std::vector<std::string>& record_args) {
impl_->StartRecording(record_args);
}
void ProfileSession::PauseRecording() {
impl_->PauseRecording();
}
void ProfileSession::ResumeRecording() {
impl_->ResumeRecording();
}
void ProfileSession::StopRecording() {
impl_->StopRecording();
}
} // namespace simpleperf

View File

@ -0,0 +1,161 @@
/*
* Copyright (C) 2019 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include <sys/types.h>
#include <unistd.h>
#include <string>
#include <vector>
// A C++ API used to control simpleperf recording.
namespace simpleperf {
/**
* RecordOptions sets record options used by ProfileSession. The options are
* converted to a string list in toRecordArgs(), which is then passed to
* `simpleperf record` cmd. Run `simpleperf record -h` or
* `run_simpleperf_on_device.py record -h` for help messages.
*
* Example:
* RecordOptions options;
* options.setDuration(3).recordDwarfCallGraph().setOutputFilename("perf.data");
* ProfileSession session;
* session.startRecording(options);
*/
class RecordOptionsImpl;
class RecordOptions {
public:
RecordOptions();
~RecordOptions();
/**
* Set output filename. Default is perf-<month>-<day>-<hour>-<minute>-<second>.data.
* The file will be generated under simpleperf_data/.
*/
RecordOptions& SetOutputFilename(const std::string& filename);
/**
* Set event to record. Default is cpu-cycles. See `simpleperf list` for all available events.
*/
RecordOptions& SetEvent(const std::string& event);
/**
* Set how many samples to generate each second running. Default is 4000.
*/
RecordOptions& SetSampleFrequency(size_t freq);
/**
* Set record duration. The record stops after `durationInSecond` seconds. By default,
* record stops only when stopRecording() is called.
*/
RecordOptions& SetDuration(double duration_in_second);
/**
* Record some threads in the app process. By default, record all threads in the process.
*/
RecordOptions& SetSampleThreads(const std::vector<pid_t>& threads);
/**
* Record dwarf based call graph. It is needed to get Java callstacks.
*/
RecordOptions& RecordDwarfCallGraph();
/**
* Record frame pointer based call graph. It is suitable to get C++ callstacks on 64bit devices.
*/
RecordOptions& RecordFramePointerCallGraph();
/**
* Trace context switch info to show where threads spend time off cpu.
*/
RecordOptions& TraceOffCpu();
/**
* Translate record options into arguments for `simpleperf record` cmd.
*/
std::vector<std::string> ToRecordArgs() const;
private:
RecordOptionsImpl* impl_;
};
/**
* ProfileSession uses `simpleperf record` cmd to generate a recording file.
* It allows users to start recording with some options, pause/resume recording
* to only profile interested code, and stop recording.
*
* Example:
* RecordOptions options;
* options.setDwarfCallGraph();
* ProfileSession session;
* session.StartRecording(options);
* sleep(1);
* session.PauseRecording();
* sleep(1);
* session.ResumeRecording();
* sleep(1);
* session.StopRecording();
*
* It aborts when error happens. To read error messages of simpleperf record
* process, filter logcat with `simpleperf`.
*/
class ProfileSessionImpl;
class ProfileSession {
public:
/**
* @param appDataDir the same as android.content.Context.getDataDir().
* ProfileSession stores profiling data in appDataDir/simpleperf_data/.
*/
ProfileSession(const std::string& app_data_dir);
/**
* ProfileSession assumes appDataDir as /data/data/app_package_name.
*/
ProfileSession();
~ProfileSession();
/**
* Start recording.
* @param options RecordOptions
*/
void StartRecording(const RecordOptions& options);
/**
* Start recording.
* @param args arguments for `simpleperf record` cmd.
*/
void StartRecording(const std::vector<std::string>& record_args);
/**
* Pause recording. No samples are generated in paused state.
*/
void PauseRecording();
/**
* Resume a paused session.
*/
void ResumeRecording();
/**
* Stop recording and generate a recording file under appDataDir/simpleperf_data/.
*/
void StopRecording();
private:
ProfileSessionImpl* impl_;
};
} // namespace simpleperf

View File

@ -0,0 +1,383 @@
/*
* Copyright (C) 2019 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.simpleperf;
import android.os.Build;
import android.system.Os;
import android.system.OsConstants;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.annotation.RequiresApi;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
/**
* <p>
* This class uses `simpleperf record` cmd to generate a recording file.
* It allows users to start recording with some options, pause/resume recording
* to only profile interested code, and stop recording.
* </p>
*
* <p>
* Example:
* RecordOptions options = new RecordOptions();
* options.setDwarfCallGraph();
* ProfileSession session = new ProfileSession();
* session.StartRecording(options);
* Thread.sleep(1000);
* session.PauseRecording();
* Thread.sleep(1000);
* session.ResumeRecording();
* Thread.sleep(1000);
* session.StopRecording();
* </p>
*
* <p>
* It throws an Error when error happens. To read error messages of simpleperf record
* process, filter logcat with `simpleperf`.
* </p>
*/
@RequiresApi(28)
public class ProfileSession {
private static final String SIMPLEPERF_PATH_IN_IMAGE = "/system/bin/simpleperf";
enum State {
NOT_YET_STARTED,
STARTED,
PAUSED,
STOPPED,
}
private State mState = State.NOT_YET_STARTED;
private final String mAppDataDir;
private String mSimpleperfPath;
private final String mSimpleperfDataDir;
private Process mSimpleperfProcess;
private boolean mTraceOffCpu = false;
/**
* @param appDataDir the same as android.content.Context.getDataDir().
* ProfileSession stores profiling data in appDataDir/simpleperf_data/.
*/
public ProfileSession(@NonNull String appDataDir) {
mAppDataDir = appDataDir;
mSimpleperfDataDir = appDataDir + "/simpleperf_data";
}
/**
* ProfileSession assumes appDataDir as /data/data/app_package_name.
*/
public ProfileSession() {
String packageName;
try {
String s = readInputStream(new FileInputStream("/proc/self/cmdline"));
for (int i = 0; i < s.length(); i++) {
if (s.charAt(i) == '\0') {
s = s.substring(0, i);
break;
}
}
packageName = s;
} catch (IOException e) {
throw new Error("failed to find packageName: " + e.getMessage());
}
if (packageName.isEmpty()) {
throw new Error("failed to find packageName");
}
final int AID_USER_OFFSET = 100000;
int uid = Os.getuid();
if (uid >= AID_USER_OFFSET) {
int user_id = uid / AID_USER_OFFSET;
mAppDataDir = "/data/user/" + user_id + "/" + packageName;
} else {
mAppDataDir = "/data/data/" + packageName;
}
mSimpleperfDataDir = mAppDataDir + "/simpleperf_data";
}
/**
* Start recording.
* @param options RecordOptions
*/
public void startRecording(@NonNull RecordOptions options) {
startRecording(options.toRecordArgs());
}
/**
* Start recording.
* @param args arguments for `simpleperf record` cmd.
*/
public synchronized void startRecording(@NonNull List<String> args) {
if (mState != State.NOT_YET_STARTED) {
throw new IllegalStateException("startRecording: session in wrong state " + mState);
}
for (String arg : args) {
if (arg.equals("--trace-offcpu")) {
mTraceOffCpu = true;
}
}
mSimpleperfPath = findSimpleperf();
checkIfPerfEnabled();
createSimpleperfDataDir();
createSimpleperfProcess(mSimpleperfPath, args);
mState = State.STARTED;
}
/**
* Pause recording. No samples are generated in paused state.
*/
public synchronized void pauseRecording() {
if (mState != State.STARTED) {
throw new IllegalStateException("pauseRecording: session in wrong state " + mState);
}
if (mTraceOffCpu) {
throw new AssertionError(
"--trace-offcpu option doesn't work well with pause/resume recording");
}
sendCmd("pause");
mState = State.PAUSED;
}
/**
* Resume a paused session.
*/
public synchronized void resumeRecording() {
if (mState != State.PAUSED) {
throw new IllegalStateException("resumeRecording: session in wrong state " + mState);
}
sendCmd("resume");
mState = State.STARTED;
}
/**
* Stop recording and generate a recording file under appDataDir/simpleperf_data/.
*/
public synchronized void stopRecording() {
if (mState != State.STARTED && mState != State.PAUSED) {
throw new IllegalStateException("stopRecording: session in wrong state " + mState);
}
if (Build.VERSION.SDK_INT == Build.VERSION_CODES.P + 1
&& mSimpleperfPath.equals(SIMPLEPERF_PATH_IN_IMAGE)) {
// The simpleperf shipped on Android Q contains a bug, which may make it abort if
// calling simpleperfProcess.destroy().
destroySimpleperfProcessWithoutClosingStdin();
} else {
mSimpleperfProcess.destroy();
}
try {
int exitCode = mSimpleperfProcess.waitFor();
if (exitCode != 0) {
throw new AssertionError("simpleperf exited with error: " + exitCode);
}
} catch (InterruptedException e) {
}
mSimpleperfProcess = null;
mState = State.STOPPED;
}
private void destroySimpleperfProcessWithoutClosingStdin() {
// In format "Process[pid=? ..."
String s = mSimpleperfProcess.toString();
final String prefix = "Process[pid=";
if (s.startsWith(prefix)) {
int startIndex = prefix.length();
int endIndex = s.indexOf(',');
if (endIndex > startIndex) {
int pid = Integer.parseInt(s.substring(startIndex, endIndex).trim());
android.os.Process.sendSignal(pid, OsConstants.SIGTERM);
return;
}
}
mSimpleperfProcess.destroy();
}
private String readInputStream(InputStream in) {
BufferedReader reader = new BufferedReader(new InputStreamReader(in));
String result = reader.lines().collect(Collectors.joining("\n"));
try {
reader.close();
} catch (IOException e) {
}
return result;
}
private String findSimpleperf() {
// 1. Try /data/local/tmp/simpleperf. Probably it's newer than /system/bin/simpleperf.
String simpleperfPath = findSimpleperfInTempDir();
if (simpleperfPath != null) {
return simpleperfPath;
}
// 2. Try /system/bin/simpleperf, which is available on Android >= Q.
simpleperfPath = SIMPLEPERF_PATH_IN_IMAGE;
if (isExecutableFile(simpleperfPath)) {
return simpleperfPath;
}
throw new Error("can't find simpleperf on device. Please run api_profiler.py.");
}
private boolean isExecutableFile(@NonNull String path) {
File file = new File(path);
return file.canExecute();
}
@Nullable
private String findSimpleperfInTempDir() {
String path = "/data/local/tmp/simpleperf";
File file = new File(path);
if (!file.isFile()) {
return null;
}
// Copy it to app dir to execute it.
String toPath = mAppDataDir + "/simpleperf";
try {
Process process = new ProcessBuilder()
.command("cp", path, toPath).start();
process.waitFor();
} catch (Exception e) {
return null;
}
if (!isExecutableFile(toPath)) {
return null;
}
// For apps with target sdk >= 29, executing app data file isn't allowed.
// For android R, app context isn't allowed to use perf_event_open.
// So test executing downloaded simpleperf.
try {
Process process = new ProcessBuilder().command(toPath, "list", "sw").start();
process.waitFor();
String data = readInputStream(process.getInputStream());
if (!data.contains("cpu-clock")) {
return null;
}
} catch (Exception e) {
return null;
}
return toPath;
}
private void checkIfPerfEnabled() {
if (getProperty("persist.simpleperf.profile_app_uid").equals("" + Os.getuid())) {
String timeStr = getProperty("persist.simpleperf.profile_app_expiration_time");
if (!timeStr.isEmpty()) {
try {
long expirationTime = Long.parseLong(timeStr);
if (expirationTime > System.currentTimeMillis() / 1000) {
return;
}
} catch (NumberFormatException e) {
}
}
}
if (getProperty("security.perf_harden") == "1") {
throw new Error("Recording app isn't enabled on the device."
+ " Please run api_profiler.py.");
}
}
private String getProperty(String name) {
String value;
Process process;
try {
process = new ProcessBuilder()
.command("/system/bin/getprop", name).start();
} catch (IOException e) {
return "";
}
try {
process.waitFor();
} catch (InterruptedException e) {
}
return readInputStream(process.getInputStream());
}
private void createSimpleperfDataDir() {
File file = new File(mSimpleperfDataDir);
if (!file.isDirectory()) {
file.mkdir();
}
}
private void createSimpleperfProcess(String simpleperfPath, List<String> recordArgs) {
// 1. Prepare simpleperf arguments.
ArrayList<String> args = new ArrayList<>();
args.add(simpleperfPath);
args.add("record");
args.add("--log-to-android-buffer");
args.add("--log");
args.add("debug");
args.add("--stdio-controls-profiling");
args.add("--in-app");
args.add("--tracepoint-events");
args.add("/data/local/tmp/tracepoint_events");
args.addAll(recordArgs);
// 2. Create the simpleperf process.
ProcessBuilder pb = new ProcessBuilder(args).directory(new File(mSimpleperfDataDir));
try {
mSimpleperfProcess = pb.start();
} catch (IOException e) {
throw new Error("failed to create simpleperf process: " + e.getMessage());
}
// 3. Wait until simpleperf starts recording.
String startFlag = readReply();
if (!startFlag.equals("started")) {
throw new Error("failed to receive simpleperf start flag");
}
}
private void sendCmd(@NonNull String cmd) {
cmd += "\n";
try {
mSimpleperfProcess.getOutputStream().write(cmd.getBytes());
mSimpleperfProcess.getOutputStream().flush();
} catch (IOException e) {
throw new Error("failed to send cmd to simpleperf: " + e.getMessage());
}
if (!readReply().equals("ok")) {
throw new Error("failed to run cmd in simpleperf: " + cmd);
}
}
@NonNull
private String readReply() {
// Read one byte at a time to stop at line break or EOF. BufferedReader will try to read
// more than available and make us blocking, so don't use it.
String s = "";
while (true) {
int c = -1;
try {
c = mSimpleperfProcess.getInputStream().read();
} catch (IOException e) {
}
if (c == -1 || c == '\n') {
break;
}
s += (char) c;
}
return s;
}
}

View File

@ -0,0 +1,196 @@
/*
* Copyright (C) 2019 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.simpleperf;
import android.system.Os;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.annotation.RequiresApi;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.List;
/**
* <p>
* This class sets record options used by ProfileSession. The options are
* converted to a string list in toRecordArgs(), which is then passed to
* `simpleperf record` cmd. Run `simpleperf record -h` or
* `run_simpleperf_on_device.py record -h` for help messages.
* </p>
*
* <p>
* Example:
* RecordOptions options = new RecordOptions();
* options.setDuration(3).recordDwarfCallGraph().setOutputFilename("perf.data");
* ProfileSession session = new ProfileSession();
* session.startRecording(options);
* </p>
*/
@RequiresApi(28)
public class RecordOptions {
/**
* Set output filename. Default is perf-<month>-<day>-<hour>-<minute>-<second>.data.
* The file will be generated under simpleperf_data/.
*/
@NonNull
public RecordOptions setOutputFilename(@NonNull String filename) {
mOutputFilename = filename;
return this;
}
/**
* Set event to record. Default is cpu-cycles. See `simpleperf list` for all available events.
*/
@NonNull
public RecordOptions setEvent(@NonNull String event) {
mEvent = event;
return this;
}
/**
* Set how many samples to generate each second running. Default is 4000.
*/
@NonNull
public RecordOptions setSampleFrequency(int freq) {
mFreq = freq;
return this;
}
/**
* Set record duration. The record stops after `durationInSecond` seconds. By default,
* record stops only when stopRecording() is called.
*/
@NonNull
public RecordOptions setDuration(double durationInSecond) {
mDurationInSeconds = durationInSecond;
return this;
}
/**
* Record some threads in the app process. By default, record all threads in the process.
*/
@NonNull
public RecordOptions setSampleThreads(@NonNull List<Integer> threads) {
mThreads.addAll(threads);
return this;
}
/**
* Record dwarf based call graph. It is needed to get Java callstacks.
*/
@NonNull
public RecordOptions recordDwarfCallGraph() {
mDwarfCallGraph = true;
mFpCallGraph = false;
return this;
}
/**
* Record frame pointer based call graph. It is suitable to get C++ callstacks on 64bit devices.
*/
@NonNull
public RecordOptions recordFramePointerCallGraph() {
mFpCallGraph = true;
mDwarfCallGraph = false;
return this;
}
/**
* Trace context switch info to show where threads spend time off cpu.
*/
@NonNull
public RecordOptions traceOffCpu() {
mTraceOffCpu = true;
return this;
}
/**
* Translate record options into arguments for `simpleperf record` cmd.
*/
@NonNull
public List<String> toRecordArgs() {
ArrayList<String> args = new ArrayList<>();
String filename = mOutputFilename;
if (filename == null) {
filename = getDefaultOutputFilename();
}
args.add("-o");
args.add(filename);
args.add("-e");
args.add(mEvent);
args.add("-f");
args.add(String.valueOf(mFreq));
if (mDurationInSeconds != 0.0) {
args.add("--duration");
args.add(String.valueOf(mDurationInSeconds));
}
if (mThreads.isEmpty()) {
args.add("-p");
args.add(String.valueOf(Os.getpid()));
} else {
String s = "";
for (int i = 0; i < mThreads.size(); i++) {
if (i > 0) {
s += ",";
}
s += mThreads.get(i).toString();
}
args.add("-t");
args.add(s);
}
if (mDwarfCallGraph) {
args.add("-g");
} else if (mFpCallGraph) {
args.add("--call-graph");
args.add("fp");
}
if (mTraceOffCpu) {
args.add("--trace-offcpu");
}
return args;
}
private String getDefaultOutputFilename() {
LocalDateTime time = LocalDateTime.now();
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("'perf'-MM-dd-HH-mm-ss'.data'");
return time.format(formatter);
}
@Nullable
private String mOutputFilename;
@NonNull
private String mEvent = "cpu-cycles";
private int mFreq = 4000;
private double mDurationInSeconds = 0.0;
@NonNull
private ArrayList<Integer> mThreads = new ArrayList<>();
private boolean mDwarfCallGraph = false;
private boolean mFpCallGraph = false;
private boolean mTraceOffCpu = false;
}

View File

@ -0,0 +1,547 @@
#!/usr/bin/env python3
#
# Copyright (C) 2016 The Android Open Source Project
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
"""app_profiler.py: Record cpu profiling data of an android app or native program.
It downloads simpleperf on device, uses it to collect profiling data on the selected app,
and pulls profiling data and related binaries on host.
"""
import logging
import os
import os.path
import re
import subprocess
import sys
import time
from typing import Optional
from simpleperf_utils import (
AdbHelper, BaseArgumentParser, bytes_to_str, extant_dir, get_script_dir, get_target_binary_path,
log_exit, ReadElf, remove, str_to_bytes)
NATIVE_LIBS_DIR_ON_DEVICE = '/data/local/tmp/native_libs/'
SHELL_PS_UID_PATTERN = re.compile(r'USER.*\nu(\d+)_.*')
class HostElfEntry(object):
"""Represent a native lib on host in NativeLibDownloader."""
def __init__(self, path, name, score):
self.path = path
self.name = name
self.score = score
def __repr__(self):
return self.__str__()
def __str__(self):
return '[path: %s, name %s, score %s]' % (self.path, self.name, self.score)
class NativeLibDownloader(object):
"""Download native libs on device.
1. Collect info of all native libs in the native_lib_dir on host.
2. Check the available native libs in /data/local/tmp/native_libs on device.
3. Sync native libs on device.
"""
def __init__(self, ndk_path, device_arch, adb):
self.adb = adb
self.readelf = ReadElf(ndk_path)
self.device_arch = device_arch
self.need_archs = self._get_need_archs()
self.host_build_id_map = {} # Map from build_id to HostElfEntry.
self.device_build_id_map = {} # Map from build_id to relative_path on device.
# Map from filename to HostElfEntry for elf files without build id.
self.no_build_id_file_map = {}
self.name_count_map = {} # Used to give a unique name for each library.
self.dir_on_device = NATIVE_LIBS_DIR_ON_DEVICE
self.build_id_list_file = 'build_id_list'
def _get_need_archs(self):
"""Return the archs of binaries needed on device."""
if self.device_arch == 'arm64':
return ['arm', 'arm64']
if self.device_arch == 'arm':
return ['arm']
if self.device_arch == 'x86_64':
return ['x86', 'x86_64']
if self.device_arch == 'x86':
return ['x86']
return []
def collect_native_libs_on_host(self, native_lib_dir):
self.host_build_id_map.clear()
for root, _, files in os.walk(native_lib_dir):
for name in files:
if not name.endswith('.so'):
continue
self.add_native_lib_on_host(os.path.join(root, name), name)
def add_native_lib_on_host(self, path, name):
arch = self.readelf.get_arch(path)
if arch not in self.need_archs:
return
sections = self.readelf.get_sections(path)
score = 0
if '.debug_info' in sections:
score = 3
elif '.gnu_debugdata' in sections:
score = 2
elif '.symtab' in sections:
score = 1
build_id = self.readelf.get_build_id(path)
if build_id:
entry = self.host_build_id_map.get(build_id)
if entry:
if entry.score < score:
entry.path = path
entry.score = score
else:
repeat_count = self.name_count_map.get(name, 0)
self.name_count_map[name] = repeat_count + 1
unique_name = name if repeat_count == 0 else name + '_' + str(repeat_count)
self.host_build_id_map[build_id] = HostElfEntry(path, unique_name, score)
else:
entry = self.no_build_id_file_map.get(name)
if entry:
if entry.score < score:
entry.path = path
entry.score = score
else:
self.no_build_id_file_map[name] = HostElfEntry(path, name, score)
def collect_native_libs_on_device(self):
self.device_build_id_map.clear()
self.adb.check_run(['shell', 'mkdir', '-p', self.dir_on_device])
if os.path.exists(self.build_id_list_file):
os.remove(self.build_id_list_file)
result, output = self.adb.run_and_return_output(['shell', 'ls', self.dir_on_device])
if not result:
return
file_set = set(output.strip().split())
if self.build_id_list_file not in file_set:
return
self.adb.run(['pull', self.dir_on_device + self.build_id_list_file])
if os.path.exists(self.build_id_list_file):
with open(self.build_id_list_file, 'rb') as fh:
for line in fh.readlines():
line = bytes_to_str(line).strip()
items = line.split('=')
if len(items) == 2:
build_id, filename = items
if filename in file_set:
self.device_build_id_map[build_id] = filename
remove(self.build_id_list_file)
def sync_native_libs_on_device(self):
# Push missing native libs on device.
for build_id in self.host_build_id_map:
if build_id not in self.device_build_id_map:
entry = self.host_build_id_map[build_id]
self.adb.check_run(['push', entry.path, self.dir_on_device + entry.name])
# Remove native libs not exist on host.
for build_id in self.device_build_id_map:
if build_id not in self.host_build_id_map:
name = self.device_build_id_map[build_id]
self.adb.run(['shell', 'rm', self.dir_on_device + name])
# Push new build_id_list on device.
with open(self.build_id_list_file, 'wb') as fh:
for build_id in self.host_build_id_map:
s = str_to_bytes('%s=%s\n' % (build_id, self.host_build_id_map[build_id].name))
fh.write(s)
self.adb.check_run(['push', self.build_id_list_file,
self.dir_on_device + self.build_id_list_file])
os.remove(self.build_id_list_file)
# Push elf files without build id on device.
for entry in self.no_build_id_file_map.values():
target = self.dir_on_device + entry.name
# Skip download if we have a file with the same name and size on device.
result, output = self.adb.run_and_return_output(['shell', 'ls', '-l', target])
if result:
items = output.split()
if len(items) > 5:
try:
file_size = int(items[4])
except ValueError:
file_size = 0
if file_size == os.path.getsize(entry.path):
continue
self.adb.check_run(['push', entry.path, target])
class ProfilerBase(object):
"""Base class of all Profilers."""
def __init__(self, args):
self.args = args
self.adb = AdbHelper(enable_switch_to_root=not args.disable_adb_root)
if not self.adb.is_device_available():
log_exit('No Android device is connected via ADB.')
self.is_root_device = self.adb.switch_to_root()
self.android_version = self.adb.get_android_version()
if self.android_version < 7:
log_exit("""app_profiler.py isn't supported on Android < N, please switch to use
simpleperf binary directly.""")
self.device_arch = self.adb.get_device_arch()
self.record_subproc = None
def profile(self):
logging.info('prepare profiling')
self.prepare()
logging.info('start profiling')
self.start()
self.wait_profiling()
logging.info('collect profiling data')
self.collect_profiling_data()
logging.info('profiling is finished.')
def prepare(self):
"""Prepare recording. """
self.download_simpleperf()
if self.args.native_lib_dir:
self.download_libs()
def download_simpleperf(self):
simpleperf_binary = get_target_binary_path(self.device_arch, 'simpleperf')
self.adb.check_run(['push', simpleperf_binary, '/data/local/tmp'])
self.adb.check_run(['shell', 'chmod', 'a+x', '/data/local/tmp/simpleperf'])
def download_libs(self):
downloader = NativeLibDownloader(self.args.ndk_path, self.device_arch, self.adb)
downloader.collect_native_libs_on_host(self.args.native_lib_dir)
downloader.collect_native_libs_on_device()
downloader.sync_native_libs_on_device()
def start(self):
raise NotImplementedError
def start_profiling(self, target_args):
"""Start simpleperf record process on device."""
args = ['/data/local/tmp/simpleperf', 'record', '-o', '/data/local/tmp/perf.data',
self.args.record_options]
if self.adb.run(['shell', 'ls', NATIVE_LIBS_DIR_ON_DEVICE]):
args += ['--symfs', NATIVE_LIBS_DIR_ON_DEVICE]
args += ['--log', self.args.log]
args += target_args
adb_args = [self.adb.adb_path, 'shell'] + args
logging.info('run adb cmd: %s' % adb_args)
self.record_subproc = subprocess.Popen(adb_args)
def wait_profiling(self):
"""Wait until profiling finishes, or stop profiling when user presses Ctrl-C."""
returncode = None
try:
returncode = self.record_subproc.wait()
except KeyboardInterrupt:
self.stop_profiling()
self.record_subproc = None
# Don't check return value of record_subproc. Because record_subproc also
# receives Ctrl-C, and always returns non-zero.
returncode = 0
logging.debug('profiling result [%s]' % (returncode == 0))
if returncode != 0:
log_exit('Failed to record profiling data.')
def stop_profiling(self):
"""Stop profiling by sending SIGINT to simpleperf, and wait until it exits
to make sure perf.data is completely generated."""
has_killed = False
while True:
(result, _) = self.adb.run_and_return_output(['shell', 'pidof', 'simpleperf'])
if not result:
break
if not has_killed:
has_killed = True
self.adb.run_and_return_output(['shell', 'pkill', '-l', '2', 'simpleperf'])
time.sleep(1)
def collect_profiling_data(self):
self.adb.check_run_and_return_output(['pull', '/data/local/tmp/perf.data',
self.args.perf_data_path])
if not self.args.skip_collect_binaries:
binary_cache_args = [sys.executable,
os.path.join(get_script_dir(), 'binary_cache_builder.py')]
binary_cache_args += ['-i', self.args.perf_data_path, '--log', self.args.log]
if self.args.native_lib_dir:
binary_cache_args += ['-lib', self.args.native_lib_dir]
if self.args.disable_adb_root:
binary_cache_args += ['--disable_adb_root']
if self.args.ndk_path:
binary_cache_args += ['--ndk_path', self.args.ndk_path]
subprocess.check_call(binary_cache_args)
class AppProfiler(ProfilerBase):
"""Profile an Android app."""
def prepare(self):
super(AppProfiler, self).prepare()
self.app_versioncode = self.get_app_versioncode()
if self.args.compile_java_code:
self.compile_java_code()
def get_app_versioncode(self) -> Optional[str]:
result, output = self.adb.run_and_return_output(
['shell', 'pm', 'list', 'packages', '--show-versioncode'])
if not result:
return None
prefix = f'package:{self.args.app} '
for line in output.splitlines():
if line.startswith(prefix):
pos = line.find('versionCode:')
if pos != -1:
return line[pos + len('versionCode:'):].strip()
return None
def compile_java_code(self):
self.kill_app_process()
# Fully compile Java code on Android >= N.
self.adb.set_property('debug.generate-debug-info', 'true')
self.adb.check_run(['shell', 'cmd', 'package', 'compile', '-f', '-m', 'speed',
self.args.app])
def kill_app_process(self):
if self.find_app_process():
self.adb.check_run(['shell', 'am', 'force-stop', self.args.app])
count = 0
while True:
time.sleep(1)
pid = self.find_app_process()
if not pid:
break
count += 1
if count >= 5:
logging.info('unable to kill %s, skipping...' % self.args.app)
break
# When testing on Android N, `am force-stop` sometimes can't kill
# com.example.simpleperf.simpleperfexampleofkotlin. So use kill when this happens.
if count >= 3:
self.run_in_app_dir(['kill', '-9', str(pid)])
def find_app_process(self):
result, pidof_output = self.adb.run_and_return_output(
['shell', 'pidof', self.args.app])
if not result:
return None
result, current_user = self.adb.run_and_return_output(
['shell', 'am', 'get-current-user'])
if not result:
return None
pids = pidof_output.split()
for pid in pids:
result, ps_output = self.adb.run_and_return_output(
['shell', 'ps', '-p', pid, '-o', 'USER'])
if not result:
return None
uid = SHELL_PS_UID_PATTERN.search(ps_output).group(1)
if uid == current_user.strip():
return int(pid)
return None
def run_in_app_dir(self, args):
if self.is_root_device:
adb_args = ['shell', 'cd /data/data/' + self.args.app + ' && ' + (' '.join(args))]
else:
adb_args = ['shell', 'run-as', self.args.app] + args
return self.adb.run_and_return_output(adb_args)
def start(self):
if self.args.launch or self.args.activity or self.args.test:
self.kill_app_process()
args = ['--app', self.args.app]
if self.app_versioncode:
args += ['--add-meta-info', f'app_versioncode={self.app_versioncode}']
self.start_profiling(args)
if self.args.launch:
self.start_app()
if self.args.activity:
self.start_activity()
elif self.args.test:
self.start_test()
# else: no need to start an activity or test.
def start_app(self):
result = self.adb.run(['shell', 'monkey', '-p', self.args.app, '1'])
if not result:
self.record_subproc.terminate()
log_exit(f"Can't start {self.args.app}")
def start_activity(self):
activity = self.args.app + '/' + self.args.activity
result = self.adb.run(['shell', 'am', 'start', '-n', activity])
if not result:
self.record_subproc.terminate()
log_exit("Can't start activity %s" % activity)
def start_test(self):
runner = self.args.app + '/androidx.test.runner.AndroidJUnitRunner'
result = self.adb.run(['shell', 'am', 'instrument', '-e', 'class',
self.args.test, runner])
if not result:
self.record_subproc.terminate()
log_exit("Can't start instrumentation test %s" % self.args.test)
class NativeProgramProfiler(ProfilerBase):
"""Profile a native program."""
def start(self):
logging.info('Waiting for native process %s' % self.args.native_program)
while True:
(result, pid) = self.adb.run_and_return_output(['shell', 'pidof',
self.args.native_program])
if not result:
# Wait for 1 millisecond.
time.sleep(0.001)
else:
self.start_profiling(['-p', str(int(pid))])
break
class NativeCommandProfiler(ProfilerBase):
"""Profile running a native command."""
def start(self):
self.start_profiling([self.args.cmd])
class NativeProcessProfiler(ProfilerBase):
"""Profile processes given their pids."""
def start(self):
self.start_profiling(['-p', ','.join(self.args.pid)])
class NativeThreadProfiler(ProfilerBase):
"""Profile threads given their tids."""
def start(self):
self.start_profiling(['-t', ','.join(self.args.tid)])
class SystemWideProfiler(ProfilerBase):
"""Profile system wide."""
def start(self):
self.start_profiling(['-a'])
def main():
parser = BaseArgumentParser(description=__doc__)
target_group = parser.add_argument_group(title='Select profiling target'
).add_mutually_exclusive_group(required=True)
target_group.add_argument('-p', '--app', help="""Profile an Android app, given the package name.
Like `-p com.example.android.myapp`.""")
target_group.add_argument('-np', '--native_program', help="""Profile a native program running on
the Android device. Like `-np surfaceflinger`.""")
target_group.add_argument('-cmd', help="""Profile running a command on the Android device.
Like `-cmd "pm -l"`.""")
target_group.add_argument('--pid', nargs='+', help="""Profile native processes running on device
given their process ids.""")
target_group.add_argument('--tid', nargs='+', help="""Profile native threads running on device
given their thread ids.""")
target_group.add_argument('--system_wide', action='store_true', help="""Profile system wide.""")
app_target_group = parser.add_argument_group(title='Extra options for profiling an app')
app_target_group.add_argument('--compile_java_code', action='store_true', help="""Used with -p.
On Android N and Android O, we need to compile Java code into
native instructions to profile Java code. Android O also needs
wrap.sh in the apk to use the native instructions.""")
app_start_group = app_target_group.add_mutually_exclusive_group()
app_start_group.add_argument('--launch', action='store_true', help="""Used with -p. Profile the
launch time of an Android app. The app will be started or
restarted.""")
app_start_group.add_argument('-a', '--activity', help="""Used with -p. Profile the launch time
of an activity in an Android app. The app will be started or
restarted to run the activity. Like `-a .MainActivity`.""")
app_start_group.add_argument('-t', '--test', help="""Used with -p. Profile the launch time of an
instrumentation test in an Android app. The app will be started or
restarted to run the instrumentation test. Like
`-t test_class_name`.""")
record_group = parser.add_argument_group('Select recording options')
record_group.add_argument('-r', '--record_options',
default='-e task-clock:u -f 1000 -g --duration 10', help="""Set
recording options for `simpleperf record` command. Use
`run_simpleperf_on_device.py record -h` to see all accepted options.
Default is "-e task-clock:u -f 1000 -g --duration 10".""")
record_group.add_argument('-lib', '--native_lib_dir', type=extant_dir,
help="""When profiling an Android app containing native libraries,
the native libraries are usually stripped and lake of symbols
and debug information to provide good profiling result. By
using -lib, you tell app_profiler.py the path storing
unstripped native libraries, and app_profiler.py will search
all shared libraries with suffix .so in the directory. Then
the native libraries will be downloaded on device and
collected in build_cache.""")
record_group.add_argument('-o', '--perf_data_path', default='perf.data',
help='The path to store profiling data. Default is perf.data.')
record_group.add_argument('-nb', '--skip_collect_binaries', action='store_true',
help="""By default we collect binaries used in profiling data from
device to binary_cache directory. It can be used to annotate
source code and disassembly. This option skips it.""")
other_group = parser.add_argument_group('Other options')
other_group.add_argument('--ndk_path', type=extant_dir,
help="""Set the path of a ndk release. app_profiler.py needs some
tools in ndk, like readelf.""")
other_group.add_argument('--disable_adb_root', action='store_true',
help="""Force adb to run in non root mode. By default, app_profiler.py
will try to switch to root mode to be able to profile released
Android apps.""")
def check_args(args):
if (not args.app) and (args.compile_java_code or args.activity or args.test):
log_exit('--compile_java_code, -a, -t can only be used when profiling an Android app.')
args = parser.parse_args()
check_args(args)
if args.app:
profiler = AppProfiler(args)
elif args.native_program:
profiler = NativeProgramProfiler(args)
elif args.cmd:
profiler = NativeCommandProfiler(args)
elif args.pid:
profiler = NativeProcessProfiler(args)
elif args.tid:
profiler = NativeThreadProfiler(args)
elif args.system_wide:
profiler = SystemWideProfiler(args)
profiler.profile()
if __name__ == '__main__':
main()

View File

@ -0,0 +1,351 @@
#!/usr/bin/env python3
#
# Copyright (C) 2016 The Android Open Source Project
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
"""binary_cache_builder.py: read perf.data, collect binaries needed by
it, and put them in binary_cache.
"""
from collections import defaultdict
import logging
import os
import os.path
from pathlib import Path
import shutil
import sys
from typing import Dict, List, Optional, Tuple, Union
from simpleperf_report_lib import ReportLib
from simpleperf_utils import (
AdbHelper, BaseArgumentParser, extant_dir, extant_file, flatten_arg_list,
ReadElf, str_to_bytes)
def is_jit_symfile(dso_name):
return dso_name.split('/')[-1].startswith('TemporaryFile')
class BinaryCache:
def __init__(self, binary_dir: Path):
self.binary_dir = binary_dir
def get_path_in_cache(self, device_path: str, build_id: str) -> Path:
""" Given a binary path in perf.data, return its corresponding path in the cache.
"""
if build_id:
filename = device_path.split('/')[-1]
# Add build id to make the filename unique.
return self.binary_dir / build_id[2:] / filename
# For elf file without build id, we can only follow its path on device. Otherwise,
# simpleperf can't find it. However, we don't prefer this way. Because:
# 1) It doesn't work for native libs loaded directly from apk
# (android:extractNativeLibs=”false”).
# 2) It may exceed path limit on windows.
if device_path.startswith('/'):
device_path = device_path[1:]
device_path = device_path.replace('/', os.sep)
return Path(os.path.join(self.binary_dir, device_path))
class BinarySource:
""" Source to find debug binaries. """
def __init__(self, readelf: ReadElf):
self.readelf = readelf
def collect_binaries(self, binaries: Dict[str, str], binary_cache: BinaryCache):
""" pull binaries needed in perf.data to binary_cache.
binaries: maps from binary path to its build_id in perf.data.
"""
raise Exception('not implemented')
def read_build_id(self, path: Path):
return self.readelf.get_build_id(path)
class BinarySourceFromDevice(BinarySource):
""" Pull binaries from device. """
def __init__(self, readelf: ReadElf, disable_adb_root: bool):
super().__init__(readelf)
self.adb = AdbHelper(enable_switch_to_root=not disable_adb_root)
def collect_binaries(self, binaries: Dict[str, str], binary_cache: BinaryCache):
if not self.adb.is_device_available():
return
for path, build_id in binaries.items():
self.collect_binary(path, build_id, binary_cache)
self.pull_kernel_symbols(binary_cache.binary_dir / 'kallsyms')
def collect_binary(self, path: str, build_id: str, binary_cache: BinaryCache):
if not path.startswith('/') or path == "//anon" or path.startswith("/dev/"):
# [kernel.kallsyms] or unknown, or something we can't find binary.
return
binary_cache_file = binary_cache.get_path_in_cache(path, build_id)
self.check_and_pull_binary(path, build_id, binary_cache_file)
def check_and_pull_binary(self, path: str, expected_build_id: str, binary_cache_file: Path):
"""If the binary_cache_file exists and has the expected_build_id, there
is no need to pull the binary from device. Otherwise, pull it.
"""
if binary_cache_file.is_file() and (
not expected_build_id or expected_build_id == self.read_build_id(binary_cache_file)
):
logging.info('use current file in binary_cache: %s', binary_cache_file)
else:
logging.info('pull file to binary_cache: %s to %s', path, binary_cache_file)
target_dir = binary_cache_file.parent
try:
os.makedirs(target_dir, exist_ok=True)
if binary_cache_file.is_file():
binary_cache_file.unlink()
success = self.pull_file_from_device(path, binary_cache_file)
except FileNotFoundError:
# It happens on windows when the filename or extension is too long.
success = False
if not success:
logging.warning('failed to pull %s from device', path)
def pull_file_from_device(self, device_path: str, host_path: Path) -> bool:
if self.adb.run(['pull', device_path, str(host_path)]):
return True
# On non-root devices, we can't pull /data/app/XXX/base.odex directly.
# Instead, we can first copy the file to /data/local/tmp, then pull it.
filename = device_path[device_path.rfind('/')+1:]
if (self.adb.run(['shell', 'cp', device_path, '/data/local/tmp']) and
self.adb.run(['pull', '/data/local/tmp/' + filename, host_path])):
self.adb.run(['shell', 'rm', '/data/local/tmp/' + filename])
return True
return False
def pull_kernel_symbols(self, file_path: Path):
if file_path.is_file():
file_path.unlink()
if self.adb.switch_to_root():
self.adb.run(['shell', 'echo', '0', '>/proc/sys/kernel/kptr_restrict'])
self.adb.run(['pull', '/proc/kallsyms', file_path])
class BinarySourceFromLibDirs(BinarySource):
""" Collect binaries from lib dirs. """
def __init__(self, readelf: ReadElf, lib_dirs: List[Path]):
super().__init__(readelf)
self.lib_dirs = lib_dirs
self.filename_map = None
self.build_id_map = None
self.binary_cache = None
def collect_binaries(self, binaries: Dict[str, str], binary_cache: BinaryCache):
self.create_filename_map(binaries)
self.create_build_id_map(binaries)
self.binary_cache = binary_cache
# Search all files in lib_dirs, and copy matching files to build_cache.
for lib_dir in self.lib_dirs:
if self.is_platform_symbols_dir(lib_dir):
self.search_platform_symbols_dir(lib_dir)
else:
self.search_dir(lib_dir)
def create_filename_map(self, binaries: Dict[str, str]):
""" Create a map mapping from filename to binaries having the name. """
self.filename_map = defaultdict(list)
for path, build_id in binaries.items():
index = path.rfind('/')
filename = path[index + 1:]
self.filename_map[filename].append((path, build_id))
def create_build_id_map(self, binaries: Dict[str, str]):
""" Create a map mapping from build id to binary path. """
self.build_id_map = {}
for path, build_id in binaries.items():
if build_id:
self.build_id_map[build_id] = path
def is_platform_symbols_dir(self, lib_dir: Path):
""" Check if lib_dir points to $ANDROID_PRODUCT_OUT/symbols. """
subdir_names = [p.name for p in lib_dir.iterdir()]
return lib_dir.name == 'symbols' and 'system' in subdir_names
def search_platform_symbols_dir(self, lib_dir: Path):
""" Platform symbols dir contains too many binaries. Reading build ids for
all of them takes a long time. So we only read build ids for binaries
having names exist in filename_map.
"""
for root, _, files in os.walk(lib_dir):
for filename in files:
binaries = self.filename_map.get(filename)
if not binaries:
continue
file_path = Path(os.path.join(root, filename))
build_id = self.read_build_id(file_path)
for path, expected_build_id in binaries:
if expected_build_id == build_id:
self.copy_to_binary_cache(file_path, build_id, path)
def search_dir(self, lib_dir: Path):
""" For a normal lib dir, it's unlikely to contain many binaries. So we can read
build ids for all binaries in it. But users may give debug binaries with a name
different from the one recorded in perf.data. So we should only rely on build id
if it is available.
"""
for root, _, files in os.walk(lib_dir):
for filename in files:
file_path = Path(os.path.join(root, filename))
build_id = self.read_build_id(file_path)
if build_id:
# For elf file with build id, use build id to match.
device_path = self.build_id_map.get(build_id)
if device_path:
self.copy_to_binary_cache(file_path, build_id, device_path)
elif self.readelf.is_elf_file(file_path):
# For elf file without build id, use filename to match.
for path, expected_build_id in self.filename_map.get(filename, []):
if not expected_build_id:
self.copy_to_binary_cache(file_path, '', path)
break
def copy_to_binary_cache(
self, from_path: Path, expected_build_id: str, device_path: str):
to_path = self.binary_cache.get_path_in_cache(device_path, expected_build_id)
if not self.need_to_copy(from_path, to_path, expected_build_id):
# The existing file in binary_cache can provide more information, so no need to copy.
return
to_dir = to_path.parent
if not to_dir.is_dir():
os.makedirs(to_dir)
logging.info('copy to binary_cache: %s to %s', from_path, to_path)
shutil.copy(from_path, to_path)
def need_to_copy(self, from_path: Path, to_path: Path, expected_build_id: str):
if not to_path.is_file() or self.read_build_id(to_path) != expected_build_id:
return True
return self.get_file_stripped_level(from_path) < self.get_file_stripped_level(to_path)
def get_file_stripped_level(self, path: Path) -> int:
"""Return stripped level of an ELF file. Larger value means more stripped."""
sections = self.readelf.get_sections(path)
if '.debug_line' in sections:
return 0
if '.symtab' in sections:
return 1
return 2
class BinaryCacheBuilder:
"""Collect all binaries needed by perf.data in binary_cache."""
def __init__(self, ndk_path: Optional[str], disable_adb_root: bool):
self.readelf = ReadElf(ndk_path)
self.device_source = BinarySourceFromDevice(self.readelf, disable_adb_root)
self.binary_cache_dir = Path('binary_cache')
self.binary_cache = BinaryCache(self.binary_cache_dir)
self.binaries = {}
def build_binary_cache(self, perf_data_path: str, symfs_dirs: List[Union[Path, str]]) -> bool:
self.binary_cache_dir.mkdir(exist_ok=True)
self.collect_used_binaries(perf_data_path)
if not self.copy_binaries_from_symfs_dirs(symfs_dirs):
return False
self.pull_binaries_from_device()
self.create_build_id_list()
return True
def collect_used_binaries(self, perf_data_path):
"""read perf.data, collect all used binaries and their build id(if available)."""
# A dict mapping from binary name to build_id
binaries = {}
lib = ReportLib()
lib.SetRecordFile(perf_data_path)
lib.SetLogSeverity('error')
while True:
sample = lib.GetNextSample()
if sample is None:
lib.Close()
break
symbols = [lib.GetSymbolOfCurrentSample()]
callchain = lib.GetCallChainOfCurrentSample()
for i in range(callchain.nr):
symbols.append(callchain.entries[i].symbol)
for symbol in symbols:
dso_name = symbol.dso_name
if dso_name not in binaries:
if is_jit_symfile(dso_name):
continue
name = 'vmlinux' if dso_name == '[kernel.kallsyms]' else dso_name
binaries[name] = lib.GetBuildIdForPath(dso_name)
self.binaries = binaries
def copy_binaries_from_symfs_dirs(self, symfs_dirs: List[Union[str, Path]]) -> bool:
if symfs_dirs:
lib_dirs: List[Path] = []
for symfs_dir in symfs_dirs:
if isinstance(symfs_dir, str):
symfs_dir = Path(symfs_dir)
if not symfs_dir.is_dir():
logging.error("can't find dir %s", symfs_dir)
return False
lib_dirs.append(symfs_dir)
lib_dir_source = BinarySourceFromLibDirs(self.readelf, lib_dirs)
lib_dir_source.collect_binaries(self.binaries, self.binary_cache)
return True
def pull_binaries_from_device(self):
self.device_source.collect_binaries(self.binaries, self.binary_cache)
def create_build_id_list(self):
""" Create build_id_list. So report scripts can find a binary by its build_id instead of
path.
"""
build_id_list_path = self.binary_cache_dir / 'build_id_list'
# Write in binary mode to avoid "\r\n" problem on windows, which can confuse simpleperf.
with open(build_id_list_path, 'wb') as fh:
for root, _, files in os.walk(self.binary_cache_dir):
for filename in files:
path = Path(os.path.join(root, filename))
build_id = self.readelf.get_build_id(path)
if build_id:
relative_path = path.relative_to(self.binary_cache_dir)
line = f'{build_id}={relative_path}\n'
fh.write(str_to_bytes(line))
def find_path_in_cache(self, device_path: str) -> Optional[Path]:
build_id = self.binaries.get(device_path)
return self.binary_cache.get_path_in_cache(device_path, build_id)
def main() -> bool:
parser = BaseArgumentParser(description="""
Pull binaries needed by perf.data from device to binary_cache directory.""")
parser.add_argument('-i', '--perf_data_path', default='perf.data', type=extant_file, help="""
The path of profiling data.""")
parser.add_argument('-lib', '--native_lib_dir', type=extant_dir, nargs='+', help="""
Path to find debug version of native shared libraries used in the app.""", action='append')
parser.add_argument('--disable_adb_root', action='store_true', help="""
Force adb to run in non root mode.""")
parser.add_argument('--ndk_path', nargs=1, help='Find tools in the ndk path.')
args = parser.parse_args()
ndk_path = None if not args.ndk_path else args.ndk_path[0]
builder = BinaryCacheBuilder(ndk_path, args.disable_adb_root)
symfs_dirs = flatten_arg_list(args.native_lib_dir)
return builder.build_binary_cache(args.perf_data_path, symfs_dirs)
if __name__ == '__main__':
sys.exit(0 if main() else 1)

View File

@ -0,0 +1,271 @@
#!/usr/bin/env python3
#
# Copyright (C) 2017 The Android Open Source Project
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
"""debug_unwind_reporter.py: report failed dwarf unwinding cases generated by debug-unwind cmd.
Below is an example using debug_unwind_reporter.py:
1. Record with "-g --keep-failed-unwinding-debug-info" option on device.
$ simpleperf record -g --keep-failed-unwinding-debug-info --app com.google.sample.tunnel \\
--duration 10
The generated perf.data can be used for normal reporting. But it also contains stack data
and binaries for debugging failed unwinding cases.
2. Generate report with debug-unwind cmd.
$ simpleperf debug-unwind -i perf.data --generate-report -o report.txt
The report contains details for each failed unwinding case. It is usually too long to
parse manually. That's why we need debug_unwind_reporter.py.
3. Use debug_unwind_reporter.py to parse the report.
$ simpleperf debug-unwind -i report.txt --summary
$ simpleperf debug-unwind -i report.txt --include-error-code 1
...
"""
import argparse
from collections import Counter, defaultdict
from simpleperf_utils import BaseArgumentParser
from texttable import Texttable
from typing import Dict, Iterator, List
class CallChainNode:
def __init__(self):
self.dso = ''
self.symbol = ''
class Sample:
""" A failed unwinding case """
def __init__(self, raw_lines: List[str]):
self.raw_lines = raw_lines
self.sample_time = 0
self.error_code = 0
self.callchain: List[CallChainNode] = []
self.parse()
def parse(self):
for line in self.raw_lines:
key, value = line.split(': ', 1)
if key == 'sample_time':
self.sample_time = int(value)
elif key == 'unwinding_error_code':
self.error_code = int(value)
elif key.startswith('dso'):
callchain_id = int(key.rsplit('_', 1)[1])
self._get_callchain_node(callchain_id).dso = value
elif key.startswith('symbol'):
callchain_id = int(key.rsplit('_', 1)[1])
self._get_callchain_node(callchain_id).symbol = value
def _get_callchain_node(self, callchain_id: int) -> CallChainNode:
callchain_id -= 1
if callchain_id == len(self.callchain):
self.callchain.append(CallChainNode())
return self.callchain[callchain_id]
class SampleFilter:
def match(self, sample: Sample) -> bool:
raise Exception('unimplemented')
class CompleteCallChainFilter(SampleFilter):
def match(self, sample: Sample) -> bool:
for node in sample.callchain:
if node.dso.endswith('libc.so') and (node.symbol in ('__libc_init', '__start_thread')):
return True
return False
class ErrorCodeFilter(SampleFilter):
def __init__(self, error_code: List[int]):
self.error_code = set(error_code)
def match(self, sample: Sample) -> bool:
return sample.error_code in self.error_code
class EndDsoFilter(SampleFilter):
def __init__(self, end_dso: List[str]):
self.end_dso = set(end_dso)
def match(self, sample: Sample) -> bool:
return sample.callchain[-1].dso in self.end_dso
class EndSymbolFilter(SampleFilter):
def __init__(self, end_symbol: List[str]):
self.end_symbol = set(end_symbol)
def match(self, sample: Sample) -> bool:
return sample.callchain[-1].symbol in self.end_symbol
class SampleTimeFilter(SampleFilter):
def __init__(self, sample_time: List[int]):
self.sample_time = set(sample_time)
def match(self, sample: Sample) -> bool:
return sample.sample_time in self.sample_time
class ReportInput:
def __init__(self):
self.exclude_filters: List[SampleFilter] = []
self.include_filters: List[SampleFilter] = []
def set_filters(self, args: argparse.Namespace):
if not args.show_callchain_fixed_by_joiner:
self.exclude_filters.append(CompleteCallChainFilter())
if args.exclude_error_code:
self.exclude_filters.append(ErrorCodeFilter(args.exclude_error_code))
if args.exclude_end_dso:
self.exclude_filters.append(EndDsoFilter(args.exclude_end_dso))
if args.exclude_end_symbol:
self.exclude_filters.append(EndSymbolFilter(args.exclude_end_symbol))
if args.exclude_sample_time:
self.exclude_filters.append(SampleTimeFilter(args.exclude_sample_time))
if args.include_error_code:
self.include_filters.append(ErrorCodeFilter(args.include_error_code))
if args.include_end_dso:
self.include_filters.append(EndDsoFilter(args.include_end_dso))
if args.include_end_symbol:
self.include_filters.append(EndSymbolFilter(args.include_end_symbol))
if args.include_sample_time:
self.include_filters.append(SampleTimeFilter(args.include_sample_time))
def get_samples(self, input_file: str) -> Iterator[Sample]:
sample_lines: List[str] = []
in_sample = False
with open(input_file, 'r') as fh:
for line in fh.readlines():
line = line.rstrip()
if line.startswith('sample_time:'):
in_sample = True
elif not line:
if in_sample:
in_sample = False
sample = Sample(sample_lines)
sample_lines = []
if self.filter_sample(sample):
yield sample
if in_sample:
sample_lines.append(line)
def filter_sample(self, sample: Sample) -> bool:
""" Return true if the input sample passes filters. """
for exclude_filter in self.exclude_filters:
if exclude_filter.match(sample):
return False
for include_filter in self.include_filters:
if not include_filter.match(sample):
return False
return True
class ReportOutput:
def report(self, sample: Sample):
pass
def end_report(self):
pass
class ReportOutputDetails(ReportOutput):
def report(self, sample: Sample):
for line in sample.raw_lines:
print(line)
print()
class ReportOutputSummary(ReportOutput):
def __init__(self):
self.error_code_counter = Counter()
self.symbol_counters: Dict[int, Counter] = defaultdict(Counter)
def report(self, sample: Sample):
symbol_key = (sample.callchain[-1].dso, sample.callchain[-1].symbol)
self.symbol_counters[sample.error_code][symbol_key] += 1
self.error_code_counter[sample.error_code] += 1
def end_report(self):
self.draw_error_code_table()
self.draw_symbol_table()
def draw_error_code_table(self):
table = Texttable()
table.set_cols_align(['l', 'c'])
table.add_row(['Count', 'Error Code'])
for error_code, count in self.error_code_counter.most_common():
table.add_row([count, error_code])
print(table.draw())
def draw_symbol_table(self):
table = Texttable()
table.set_cols_align(['l', 'c', 'l', 'l'])
table.add_row(['Count', 'Error Code', 'Dso', 'Symbol'])
for error_code, _ in self.error_code_counter.most_common():
symbol_counter = self.symbol_counters[error_code]
for symbol_key, count in symbol_counter.most_common():
dso, symbol = symbol_key
table.add_row([count, error_code, dso, symbol])
print(table.draw())
def get_args() -> argparse.Namespace:
parser = BaseArgumentParser(description=__doc__)
parser.add_argument('-i', '--input-file', required=True,
help='report file generated by debug-unwind cmd')
parser.add_argument(
'--show-callchain-fixed-by-joiner', action='store_true',
help="""By default, we don't show failed unwinding cases fixed by callchain joiner.
Use this option to show them.""")
parser.add_argument('--summary', action='store_true',
help='show summary instead of case details')
parser.add_argument('--exclude-error-code', metavar='error_code', type=int, nargs='+',
help='exclude cases with selected error code')
parser.add_argument('--exclude-end-dso', metavar='dso', nargs='+',
help='exclude cases ending at selected binary')
parser.add_argument('--exclude-end-symbol', metavar='symbol', nargs='+',
help='exclude cases ending at selected symbol')
parser.add_argument('--exclude-sample-time', metavar='time', type=int,
nargs='+', help='exclude cases with selected sample time')
parser.add_argument('--include-error-code', metavar='error_code', type=int,
nargs='+', help='include cases with selected error code')
parser.add_argument('--include-end-dso', metavar='dso', nargs='+',
help='include cases ending at selected binary')
parser.add_argument('--include-end-symbol', metavar='symbol', nargs='+',
help='include cases ending at selected symbol')
parser.add_argument('--include-sample-time', metavar='time', type=int,
nargs='+', help='include cases with selected sample time')
return parser.parse_args()
def main():
args = get_args()
report_input = ReportInput()
report_input.set_filters(args)
report_output = ReportOutputSummary() if args.summary else ReportOutputDetails()
for sample in report_input.get_samples(args.input_file):
report_output.report(sample)
report_output.end_report()
if __name__ == '__main__':
main()

View File

@ -0,0 +1,333 @@
# Simpleperf
Android Studio includes a graphical front end to Simpleperf, documented in
[Inspect CPU activity with CPU Profiler](https://developer.android.com/studio/profile/cpu-profiler).
Most users will prefer to use that instead of using Simpleperf directly.
Simpleperf is a native CPU profiling tool for Android. It can be used to profile
both Android applications and native processes running on Android. It can
profile both Java and C++ code on Android. The simpleperf executable can run on Android >=L,
and Python scripts can be used on Android >= N.
Simpleperf is part of the Android Open Source Project.
The source code is [here](https://android.googlesource.com/platform/system/extras/+/main/simpleperf/).
The latest document is [here](https://android.googlesource.com/platform/system/extras/+/main/simpleperf/doc/README.md).
[TOC]
## Introduction
An introduction slide deck is [here](./introduction.pdf).
Simpleperf contains two parts: the simpleperf executable and Python scripts.
The simpleperf executable works similar to linux-tools-perf, but has some specific features for
the Android profiling environment:
1. It collects more info in profiling data. Since the common workflow is "record on the device, and
report on the host", simpleperf not only collects samples in profiling data, but also collects
needed symbols, device info and recording time.
2. It delivers new features for recording.
1) When recording dwarf based call graph, simpleperf unwinds the stack before writing a sample
to file. This is to save storage space on the device.
2) Support tracing both on CPU time and off CPU time with --trace-offcpu option.
3) Support recording callgraphs of JITed and interpreted Java code on Android >= P.
3. It relates closely to the Android platform.
1) Is aware of Android environment, like using system properties to enable profiling, using
run-as to profile in application's context.
2) Supports reading symbols and debug information from the .gnu_debugdata section, because
system libraries are built with .gnu_debugdata section starting from Android O.
3) Supports profiling shared libraries embedded in apk files.
4) It uses the standard Android stack unwinder, so its results are consistent with all other
Android tools.
4. It builds executables and shared libraries for different usages.
1) Builds static executables on the device. Since static executables don't rely on any library,
simpleperf executables can be pushed on any Android device and used to record profiling data.
2) Builds executables on different hosts: Linux, Mac and Windows. These executables can be used
to report on hosts.
3) Builds report shared libraries on different hosts. The report library is used by different
Python scripts to parse profiling data.
Detailed documentation for the simpleperf executable is [here](#executable-commands-reference).
Python scripts are split into three parts according to their functions:
1. Scripts used for recording, like app_profiler.py, run_simpleperf_without_usb_connection.py.
2. Scripts used for reporting, like report.py, report_html.py, inferno.
3. Scripts used for parsing profiling data, like simpleperf_report_lib.py.
The python scripts are tested on Python >= 3.9. Older versions may not be supported.
Detailed documentation for the Python scripts is [here](#scripts-reference).
## Tools in simpleperf
The simpleperf executables and Python scripts are located in simpleperf/ in ndk releases, and in
system/extras/simpleperf/scripts/ in AOSP. Their functions are listed below.
bin/: contains executables and shared libraries.
bin/android/${arch}/simpleperf: static simpleperf executables used on the device.
bin/${host}/${arch}/simpleperf: simpleperf executables used on the host, only supports reporting.
bin/${host}/${arch}/libsimpleperf_report.${so/dylib/dll}: report shared libraries used on the host.
*.py, inferno, purgatorio: Python scripts used for recording and reporting. Details are in [scripts_reference.md](scripts_reference.md).
## Android application profiling
See [android_application_profiling.md](./android_application_profiling.md).
## Android platform profiling
See [android_platform_profiling.md](./android_platform_profiling.md).
## Executable commands reference
See [executable_commands_reference.md](./executable_commands_reference.md).
## Scripts reference
See [scripts_reference.md](./scripts_reference.md).
## View the profile
See [view_the_profile.md](./view_the_profile.md).
## Answers to common issues
### Support on different Android versions
On Android < N, the kernel may be too old (< 3.18) to support features like recording DWARF
based call graphs.
On Android M - O, we can only profile C++ code and fully compiled Java code.
On Android >= P, the ART interpreter supports DWARF based unwinding. So we can profile Java code.
On Android >= Q, we can used simpleperf shipped on device to profile released Android apps, with
`<profileable android:shell="true" />`.
### Comparing DWARF based and stack frame based call graphs
Simpleperf supports two ways recording call stacks with samples. One is DWARF based call graph,
the other is stack frame based call graph. Below is their comparison:
Recording DWARF based call graph:
1. Needs support of debug information in binaries.
2. Behaves normally well on both ARM and ARM64, for both Java code and C++ code.
3. Can only unwind 64K stack for each sample. So it isn't always possible to unwind to the bottom.
However, this is alleviated in simpleperf, as explained in the next section.
4. Takes more CPU time than stack frame based call graphs. So it has higher overhead, and can't
sample at very high frequency (usually <= 4000 Hz).
Recording stack frame based call graph:
1. Needs support of stack frame registers.
2. Doesn't work well on ARM. Because ARM is short of registers, and ARM and THUMB code have
different stack frame registers. So the kernel can't unwind user stack containing both ARM and
THUMB code.
3. Also doesn't work well on Java code. Because the ART compiler doesn't reserve stack frame
registers. And it can't get frames for interpreted Java code.
4. Works well when profiling native programs on ARM64. One example is profiling surfacelinger. And
usually shows complete flamegraph when it works well.
5. Takes much less CPU time than DWARF based call graphs. So the sample frequency can be 10000 Hz or
higher.
So if you need to profile code on ARM or profile Java code, DWARF based call graph is better. If you
need to profile C++ code on ARM64, stack frame based call graphs may be better. After all, you can
fisrt try DWARF based call graph, which is also the default option when `-g` is used. Because it
always produces reasonable results. If it doesn't work well enough, then try stack frame based call
graph instead.
### Fix broken DWARF based call graph
A DWARF-based call graph is generated by unwinding thread stacks. When a sample is recorded, a
kernel dumps up to 64 kilobytes of stack data. By unwinding the stack based on DWARF information,
we can get a call stack.
Two reasons may cause a broken call stack:
1. The kernel can only dump up to 64 kilobytes of stack data for each sample, but a thread can have
much larger stack. In this case, we can't unwind to the thread start point.
2. We need binaries containing DWARF call frame information to unwind stack frames. The binary
should have one of the following sections: .eh_frame, .debug_frame, .ARM.exidx or .gnu_debugdata.
To mitigate these problems,
For the missing stack data problem:
1. To alleviate it, simpleperf joins callchains (call stacks) after recording. If two callchains of
a thread have an entry containing the same ip and sp address, then simpleperf tries to join them
to make the callchains longer. So we can get more complete callchains by recording longer and
joining more samples. This doesn't guarantee to get complete call graphs. But it usually works
well.
2. Simpleperf stores samples in a buffer before unwinding them. If the bufer is low in free space,
simpleperf may decide to truncate stack data for a sample to 1K. Hopefully, this can be recovered
by callchain joiner. But when a high percentage of samples are truncated, many callchains can be
broken. We can tell if many samples are truncated in the record command output, like:
```sh
$ simpleperf record ...
simpleperf I cmd_record.cpp:809] Samples recorded: 105584 (cut 86291). Samples lost: 6501.
$ simpleperf record ...
simpleperf I cmd_record.cpp:894] Samples recorded: 7,365 (1,857 with truncated stacks).
```
There are two ways to avoid truncating samples. One is increasing the buffer size, like
`--user-buffer-size 1G`. But `--user-buffer-size` is only available on latest simpleperf. If that
option isn't available, we can use `--no-cut-samples` to disable truncating samples.
For the missing DWARF call frame info problem:
1. Most C++ code generates binaries containing call frame info, in .eh_frame or .ARM.exidx sections.
These sections are not stripped, and are usually enough for stack unwinding.
2. For C code and a small percentage of C++ code that the compiler is sure will not generate
exceptions, the call frame info is generated in .debug_frame section. .debug_frame section is
usually stripped with other debug sections. One way to fix it, is to download unstripped binaries
on device, as [here](#fix-broken-callchain-stopped-at-c-functions).
3. The compiler doesn't generate unwind instructions for function prologue and epilogue. Because
they operates stack frames and will not generate exceptions. But profiling may hit these
instructions, and fails to unwind them. This usually doesn't matter in a frame graph. But in a
time based Stack Chart (like in Android Studio and Firefox profiler), this causes stack gaps once
in a while. We can remove stack gaps via `--remove-gaps`, which is already enabled by default.
### Fix broken callchain stopped at C functions
When using dwarf based call graphs, simpleperf generates callchains during recording to save space.
The debug information needed to unwind C functions is in .debug_frame section, which is usually
stripped in native libraries in apks. To fix this, we can download unstripped version of native
libraries on device, and ask simpleperf to use them when recording.
To use simpleperf directly:
```sh
# create native_libs dir on device, and push unstripped libs in it (nested dirs are not supported).
$ adb shell mkdir /data/local/tmp/native_libs
$ adb push <unstripped_dir>/*.so /data/local/tmp/native_libs
# run simpleperf record with --symfs option.
$ adb shell simpleperf record xxx --symfs /data/local/tmp/native_libs
```
To use app_profiler.py:
```sh
$ ./app_profiler.py -lib <unstripped_dir>
```
### How to solve missing symbols in report?
The simpleperf record command collects symbols on device in perf.data. But if the native libraries
you use on device are stripped, this will result in a lot of unknown symbols in the report. A
solution is to build binary_cache on host.
```sh
# Collect binaries needed by perf.data in binary_cache/.
$ ./binary_cache_builder.py -lib NATIVE_LIB_DIR,...
```
The NATIVE_LIB_DIRs passed in -lib option are the directories containing unstripped native
libraries on host. After running it, the native libraries containing symbol tables are collected
in binary_cache/ for use when reporting.
```sh
$ ./report.py --symfs binary_cache
# report_html.py searches binary_cache/ automatically, so you don't need to
# pass it any argument.
$ ./report_html.py
```
### Show annotated source code and disassembly
To show hot places at source code and instruction level, we need to show source code and
disassembly with event count annotation. Simpleperf supports showing annotated source code and
disassembly for C++ code and fully compiled Java code. Simpleperf supports two ways to do it:
1. Through report_html.py:
1) Generate perf.data and pull it on host.
2) Generate binary_cache, containing elf files with debug information. Use -lib option to add
libs with debug info. Do it with
`binary_cache_builder.py -i perf.data -lib <dir_of_lib_with_debug_info>`.
3) Use report_html.py to generate report.html with annotated source code and disassembly,
as described [here](https://android.googlesource.com/platform/system/extras/+/main/simpleperf/doc/scripts_reference.md#report_html_py).
2. Through pprof.
1) Generate perf.data and binary_cache as above.
2) Use pprof_proto_generator.py to generate pprof proto file. `pprof_proto_generator.py`.
3) Use pprof to report a function with annotated source code, as described [here](https://android.googlesource.com/platform/system/extras/+/main/simpleperf/doc/scripts_reference.md#pprof_proto_generator_py).
### Reduce lost samples and samples with truncated stack
When using `simpleperf record`, we may see lost samples or samples with truncated stack data. Before
saving samples to a file, simpleperf uses two buffers to cache samples in memory. One is a kernel
buffer, the other is a userspace buffer. The kernel puts samples to the kernel buffer. Simpleperf
moves samples from the kernel buffer to the userspace buffer before processing them. If a buffer
overflows, we lose samples or get samples with truncated stack data. Below is an example.
```sh
$ simpleperf record -a --duration 1 -g --user-buffer-size 100k
simpleperf I cmd_record.cpp:799] Recorded for 1.00814 seconds. Start post processing.
simpleperf I cmd_record.cpp:894] Samples recorded: 79 (16 with truncated stacks).
Samples lost: 2,129 (kernelspace: 18, userspace: 2,111).
simpleperf W cmd_record.cpp:911] Lost 18.5567% of samples in kernel space, consider increasing
kernel buffer size(-m), or decreasing sample frequency(-f), or
increasing sample period(-c).
simpleperf W cmd_record.cpp:928] Lost/Truncated 97.1233% of samples in user space, consider
increasing userspace buffer size(--user-buffer-size), or
decreasing sample frequency(-f), or increasing sample period(-c).
```
In the above example, we get 79 samples, 16 of them are with truncated stack data. We lose 18
samples in the kernel buffer, and lose 2111 samples in the userspace buffer.
To reduce lost samples in the kernel buffer, we can increase kernel buffer size via `-m`. To reduce
lost samples in the userspace buffer, or reduce samples with truncated stack data, we can increase
userspace buffer size via `--user-buffer-size`.
We can also reduce samples generated in a fixed time period, like reducing sample frequency using
`-f`, reducing monitored threads, not monitoring multiple perf events at the same time.
## Bugs and contribution
Bugs and feature requests can be submitted at https://github.com/android/ndk/issues.
Patches can be uploaded to android-review.googlesource.com as [here](https://source.android.com/setup/contribute/),
or sent to email addresses listed [here](https://android.googlesource.com/platform/system/extras/+/main/simpleperf/OWNERS).
If you want to compile simpleperf C++ source code, follow below steps:
1. Download AOSP main branch as [here](https://source.android.com/setup/build/requirements).
2. Build simpleperf.
```sh
$ . build/envsetup.sh
$ lunch aosp_arm64-trunk_staging-userdebug
$ mmma system/extras/simpleperf -j30
```
If built successfully, out/target/product/generic_arm64/system/bin/simpleperf is for ARM64, and
out/target/product/generic_arm64/system/bin/simpleperf32 is for ARM.
The source code of simpleperf python scripts is in [system/extras/simpleperf/scripts](https://android.googlesource.com/platform/system/extras/+/main/simpleperf/scripts/).
Most scripts rely on simpleperf binaries to work. To update binaries for scripts (using linux
x86_64 host and android arm64 target as an example):
```sh
$ cp out/host/linux-x86/lib64/libsimpleperf_report.so system/extras/simpleperf/scripts/bin/linux/x86_64/libsimpleperf_report.so
$ cp out/target/product/generic_arm64/system/bin/simpleperf_ndk64 system/extras/simpleperf/scripts/bin/android/arm64/simpleperf
```
Then you can try the latest simpleperf scripts and binaries in system/extras/simpleperf/scripts.

View File

@ -0,0 +1,313 @@
# Android application profiling
This section shows how to profile an Android application.
Some examples are [Here](https://android.googlesource.com/platform/system/extras/+/main/simpleperf/demo/README.md).
Profiling an Android application involves three steps:
1. Prepare an Android application.
2. Record profiling data.
3. Report profiling data.
[TOC]
## Prepare an Android application
Based on the profiling situation, we may need to customize the build script to generate an apk file
specifically for profiling. Below are some suggestions.
1. If you want to profile a debug build of an application:
For the debug build type, Android studio sets android::debuggable="true" in AndroidManifest.xml,
enables JNI checks and may not optimize C/C++ code. It can be profiled by simpleperf without any
change.
2. If you want to profile a release build of an application:
For the release build type, Android studio sets android::debuggable="false" in AndroidManifest.xml,
disables JNI checks and optimizes C/C++ code. However, security restrictions mean that only apps
with android::debuggable set to true can be profiled. So simpleperf can only profile a release
build under these three circumstances:
If you are on a rooted device, you can profile any app.
If you are on Android >= Q, you can add profileableFromShell flag in AndroidManifest.xml, this makes
a released app profileable by preinstalled profiling tools. In this case, simpleperf downloaded by
adb will invoke simpleperf preinstalled in system image to profile the app.
```
<manifest ...>
<application ...>
<profileable android:shell="true" />
</application>
</manifest>
```
If you are on Android >= O, we can use [wrap.sh](https://developer.android.com/ndk/guides/wrap-script.html)
to profile a release build:
Step 1: Add android::debuggable="true" in AndroidManifest.xml to enable profiling.
```
<manifest ...>
<application android::debuggable="true" ...>
```
Step 2: Add wrap.sh in lib/`arch` directories. wrap.sh runs the app without passing any debug flags
to ART, so the app runs as a release app. wrap.sh can be done by adding the script below in
app/build.gradle.
```
android {
buildTypes {
release {
sourceSets {
release {
resources {
srcDir {
"wrap_sh_lib_dir"
}
}
}
}
}
}
}
task createWrapShLibDir
for (String abi : ["armeabi-v7a", "arm64-v8a", "x86", "x86_64"]) {
def dir = new File("app/wrap_sh_lib_dir/lib/" + abi)
dir.mkdirs()
def wrapFile = new File(dir, "wrap.sh")
wrapFile.withWriter { writer ->
writer.write('#!/system/bin/sh\n\$@\n')
}
}
}
```
3. If you want to profile C/C++ code:
Android studio strips symbol table and debug info of native libraries in the apk. So the profiling
results may contain unknown symbols or broken callgraphs. To fix this, we can pass app_profiler.py
a directory containing unstripped native libraries via the -lib option. Usually the directory can
be the path of your Android Studio project.
4. If you want to profile Java code:
On Android >= P, simpleperf supports profiling Java code, no matter whether it is executed by
the interpreter, or JITed, or compiled into native instructions. So you don't need to do anything.
On Android O, simpleperf supports profiling Java code which is compiled into native instructions,
and it also needs wrap.sh to use the compiled Java code. To compile Java code, we can pass
app_profiler.py the --compile_java_code option.
On Android N, simpleperf supports profiling Java code that is compiled into native instructions.
To compile java code, we can pass app_profiler.py the --compile_java_code option.
On Android <= M, simpleperf doesn't support profiling Java code.
Below I use application [SimpleperfExampleCpp](https://android.googlesource.com/platform/system/extras/+/main/simpleperf/demo/SimpleperfExampleCpp).
It builds an app-debug.apk for profiling.
```sh
$ git clone https://android.googlesource.com/platform/system/extras
$ cd extras/simpleperf/demo
# Open SimpleperfExampleCpp project with Android studio, and build this project
# successfully, otherwise the `./gradlew` command below will fail.
$ cd SimpleperfExampleCpp
# On windows, use "gradlew" instead.
$ ./gradlew clean assemble
$ adb install -r app/build/outputs/apk/debug/app-debug.apk
```
## Record and report profiling data
We can use [app-profiler.py](scripts_reference.md#app_profilerpy) to profile Android applications.
```sh
# Cd to the directory of simpleperf scripts. Record perf.data.
# -p option selects the profiled app using its package name.
# --compile_java_code option compiles Java code into native instructions, which isn't needed on
# Android >= P.
# -a option selects the Activity to profile.
# -lib option gives the directory to find debug native libraries.
$ ./app_profiler.py -p simpleperf.example.cpp -a .MixActivity -lib path_of_SimpleperfExampleCpp
```
This will collect profiling data in perf.data in the current directory, and related native
binaries in binary_cache/.
Normally we need to use the app when profiling, otherwise we may record no samples. But in this
case, the MixActivity starts a busy thread. So we don't need to use the app while profiling.
```sh
# Report perf.data in stdio interface.
$ ./report.py
Cmdline: /data/data/simpleperf.example.cpp/simpleperf record ...
Arch: arm64
Event: task-clock:u (type 1, config 1)
Samples: 10023
Event count: 10023000000
Overhead Command Pid Tid Shared Object Symbol
27.04% BusyThread 5703 5729 /system/lib64/libart.so art::JniMethodStart(art::Thread*)
25.87% BusyThread 5703 5729 /system/lib64/libc.so long StrToI<long, ...
...
```
[report.py](scripts_reference.md#reportpy) reports profiling data in stdio interface. If there
are a lot of unknown symbols in the report, check [here](README.md#how-to-solve-missing-symbols-in-report).
```sh
# Report perf.data in html interface.
$ ./report_html.py
# Add source code and disassembly. Change the path of source_dirs if it not correct.
$ ./report_html.py --add_source_code --source_dirs path_of_SimpleperfExampleCpp \
--add_disassembly
```
[report_html.py](scripts_reference.md#report_htmlpy) generates report in report.html, and pops up
a browser tab to show it.
## Record and report call graph
We can record and report [call graphs](executable_commands_reference.md#record-call-graphs) as below.
```sh
# Record dwarf based call graphs: add "-g" in the -r option.
$ ./app_profiler.py -p simpleperf.example.cpp \
-r "-e task-clock:u -f 1000 --duration 10 -g" -lib path_of_SimpleperfExampleCpp
# Record stack frame based call graphs: add "--call-graph fp" in the -r option.
$ ./app_profiler.py -p simpleperf.example.cpp \
-r "-e task-clock:u -f 1000 --duration 10 --call-graph fp" \
-lib path_of_SimpleperfExampleCpp
# Report call graphs in stdio interface.
$ ./report.py -g
# Report call graphs in python Tk interface.
$ ./report.py -g --gui
# Report call graphs in html interface.
$ ./report_html.py
# Report call graphs in flamegraphs.
# On Windows, use inferno.bat instead of ./inferno.sh.
$ ./inferno.sh -sc
```
## Report in html interface
We can use [report_html.py](scripts_reference.md#report_htmlpy) to show profiling results in a web browser.
report_html.py integrates chart statistics, sample table, flamegraphs, source code annotation
and disassembly annotation. It is the recommended way to show reports.
```sh
$ ./report_html.py
```
## Show flamegraph
To show flamegraphs, we need to first record call graphs. Flamegraphs are shown by
report_html.py in the "Flamegraph" tab.
We can also use [inferno](scripts_reference.md#inferno) to show flamegraphs directly.
```sh
# On Windows, use inferno.bat instead of ./inferno.sh.
$ ./inferno.sh -sc
```
We can also build flamegraphs using https://github.com/brendangregg/FlameGraph.
Please make sure you have perl installed.
```sh
$ git clone https://github.com/brendangregg/FlameGraph.git
$ ./report_sample.py --symfs binary_cache >out.perf
$ FlameGraph/stackcollapse-perf.pl out.perf >out.folded
$ FlameGraph/flamegraph.pl out.folded >a.svg
```
## Report in Android Studio
simpleperf report-sample command can convert perf.data into protobuf format accepted by
Android Studio cpu profiler. The conversion can be done either on device or on host. If you have
more symbol info on host, then prefer do it on host with --symdir option.
```sh
$ simpleperf report-sample --protobuf --show-callchain -i perf.data -o perf.trace
# Then open perf.trace in Android Studio to show it.
```
## Deobfuscate Java symbols
Java symbols may be obfuscated by ProGuard. To restore the original symbols in a report, we can
pass a Proguard mapping file to the report scripts or report-sample command via
`--proguard-mapping-file`.
```sh
$ ./report_html.py --proguard-mapping-file proguard_mapping_file.txt
```
## Record both on CPU time and off CPU time
We can [record both on CPU time and off CPU time](executable_commands_reference.md#record-both-on-cpu-time-and-off-cpu-time).
First check if trace-offcpu feature is supported on the device.
```sh
$ ./run_simpleperf_on_device.py list --show-features
dwarf-based-call-graph
trace-offcpu
```
If trace-offcpu is supported, it will be shown in the feature list. Then we can try it.
```sh
$ ./app_profiler.py -p simpleperf.example.cpp -a .SleepActivity \
-r "-g -e task-clock:u -f 1000 --duration 10 --trace-offcpu" \
-lib path_of_SimpleperfExampleCpp
$ ./report_html.py --add_disassembly --add_source_code \
--source_dirs path_of_SimpleperfExampleCpp
```
## Profile from launch
We can [profile from launch of an application](scripts_reference.md#profile-from-launch-of-an-application).
```sh
# Start simpleperf recording, then start the Activity to profile.
$ ./app_profiler.py -p simpleperf.example.cpp -a .MainActivity
# We can also start the Activity on the device manually.
# 1. Make sure the application isn't running or one of the recent apps.
# 2. Start simpleperf recording.
$ ./app_profiler.py -p simpleperf.example.cpp
# 3. Start the app manually on the device.
```
## Control recording in application code
Simpleperf supports controlling recording from application code. Below is the workflow:
1. Run `api_profiler.py prepare -p <package_name>` to allow an app recording itself using
simpleperf. By default, the permission is reset after device reboot. So we need to run the
script every time the device reboots. But on Android >= 13, we can use `--days` options to
set how long we want the permission to last.
2. Link simpleperf app_api code in the application. The app needs to be debuggable or
profileableFromShell as described [here](#prepare-an-android-application). Then the app can
use the api to start/pause/resume/stop recording. To start recording, the app_api forks a child
process running simpleperf, and uses pipe files to send commands to the child process. After
recording, a profiling data file is generated.
3. Run `api_profiler.py collect -p <package_name>` to collect profiling data files to host.
Examples are CppApi and JavaApi in [demo](https://android.googlesource.com/platform/system/extras/+/main/simpleperf/demo).
## Parse profiling data manually
We can also write python scripts to parse profiling data manually, by using
[simpleperf_report_lib.py](scripts_reference.md#simpleperf_report_libpy). Examples are report_sample.py,
report_html.py.

View File

@ -0,0 +1,109 @@
# Android platform profiling
[TOC]
## General Tips
Here are some tips for Android platform developers, who build and flash system images on rooted
devices:
1. After running `adb root`, simpleperf can be used to profile any process or system wide.
2. It is recommended to use the latest simpleperf available in AOSP main, if you are not working
on the current main branch. Scripts are in `system/extras/simpleperf/scripts`, binaries are in
`system/extras/simpleperf/scripts/bin/android`.
3. It is recommended to use `app_profiler.py` for recording, and `report_html.py` for reporting.
Below is an example.
```sh
# Record surfaceflinger process for 10 seconds with dwarf based call graph. More examples are in
# scripts reference in the doc.
$ ./app_profiler.py -np surfaceflinger -r "-g --duration 10"
# Generate html report.
$ ./report_html.py
```
4. Since Android >= O has symbols for system libraries on device, we don't need to use unstripped
binaries in `$ANDROID_PRODUCT_OUT/symbols` to report call graphs. However, they are needed to add
source code and disassembly (with line numbers) in the report. Below is an example.
```sh
# Doing recording with app_profiler.py or simpleperf on device, and generates perf.data on host.
$ ./app_profiler.py -np surfaceflinger -r "--call-graph fp --duration 10"
# Collect unstripped binaries from $ANDROID_PRODUCT_OUT/symbols to binary_cache/.
$ ./binary_cache_builder.py -lib $ANDROID_PRODUCT_OUT/symbols
# Report source code and disassembly. Disassembling all binaries is slow, so it's better to add
# --binary_filter option to only disassemble selected binaries.
$ ./report_html.py --add_source_code --source_dirs $ANDROID_BUILD_TOP --add_disassembly \
--binary_filter surfaceflinger.so
```
## Start simpleperf from system_server process
Sometimes we want to profile a process/system-wide when a special situation happens. In this case,
we can add code starting simpleperf at the point where the situation is detected.
1. Disable selinux by `adb shell setenforce 0`. Because selinux only allows simpleperf running
in shell or debuggable/profileable apps.
2. Add below code at the point where the special situation is detected.
```java
try {
// for capability check
Os.prctl(OsConstants.PR_CAP_AMBIENT, OsConstants.PR_CAP_AMBIENT_RAISE,
OsConstants.CAP_SYS_PTRACE, 0, 0);
// Write to /data instead of /data/local/tmp. Because /data can be written by system user.
Runtime.getRuntime().exec("/system/bin/simpleperf record -g -p " + String.valueOf(Process.myPid())
+ " -o /data/perf.data --duration 30 --log-to-android-buffer --log verbose");
} catch (Exception e) {
Slog.e(TAG, "error while running simpleperf");
e.printStackTrace();
}
```
## Hardware PMU counter limit
When monitoring instruction and cache related perf events (in hw/cache/raw/pmu category of list cmd),
these events are mapped to PMU counters on each cpu core. But each core only has a limited number
of PMU counters. If number of events > number of PMU counters, then the counters are multiplexed
among events, which probably isn't what we want. We can use `simpleperf stat --print-hw-counter` to
show hardware counters (per core) available on the device.
On Pixel devices, the number of PMU counters on each core is usually 7, of which 4 of them are used
by the kernel to monitor memory latency. So only 3 counters are available. It's fine to monitor up
to 3 PMU events at the same time. To monitor more than 3 events, the `--use-devfreq-counters` option
can be used to borrow from the counters used by the kernel.
## Get boot-time profile
On userdebug/eng devices, we can get boot-time profile via simpleperf.
Step 1. Customize the configuration if needed. By default, simpleperf tracks all processes
except for itself, starts at `early-init`, and stops when `sys.boot_completed` is set.
You can customize it by changing the trigger or command line flags in
`system/extras/simpleperf/simpleperf.rc`.
Step 2. Add `androidboot.simpleperf.boot_record=1` to the kernel command line.
For example, on Pixel devices, you can do
```
$ fastboot oem cmdline add androidboot.simpleperf.boot_record=1
```
Step 3. Reboot the device. When booting, init finds that the kernel command line flag is set,
so it forks a background process to run simpleperf to record boot-time profile.
init starts simpleperf at `early-init` stage, which is very soon after second-stage init starts.
Step 4. After boot, the boot-time profile is stored in /tmp/boot_perf.data. Then we can pull
the profile to host to report.
```
$ adb shell ls /tmp/boot_perf.data
/tmp/boot_perf.data
```
Following is a boot-time profile example. From timestamp, the first sample is generated at about
4.5s after booting.
![boot_time_profile](pictures/boot_time_profile.png)

Binary file not shown.

After

Width:  |  Height:  |  Size: 158 KiB

View File

@ -0,0 +1,268 @@
# Collect ETM data for AutoFDO
[TOC]
## Introduction
ETM is a hardware feature available on arm64 devices. It collects the instruction stream running on
each cpu. ARM uses ETM as an alternative for LBR (last branch record) on x86.
Simpleperf supports collecting ETM data, and converting it to input files for AutoFDO, which can
then be used for PGO (profile-guided optimization) during compilation.
On ARMv8, ETM is considered as an external debug interface (unless ARMv8.4 Self-hosted Trace
extension is impelemented). So it needs to be enabled explicitly in the bootloader, and isn't
available on user devices. For Pixel devices, it's available on EVT and DVT devices on Pixel 4,
Pixel 4a (5G) and Pixel 5. To test if it's available on other devices, you can follow commands in
this doc and see if you can record any ETM data.
## Examples
Below are examples collecting ETM data for AutoFDO. It has two steps: first recording ETM data,
second converting ETM data to AutoFDO input files.
Record ETM data:
```sh
# preparation: we need to be root to record ETM data
$ adb root
$ adb shell
redfin:/ \# cd data/local/tmp
redfin:/data/local/tmp \#
# Do a system wide collection, it writes output to perf.data.
# If only want ETM data for kernel, use `-e cs-etm:k`.
# If only want ETM data for userspace, use `-e cs-etm:u`.
redfin:/data/local/tmp \# simpleperf record -e cs-etm --duration 3 -a
# To reduce file size and time converting to AutoFDO input files, we recommend converting ETM data
# into an intermediate branch-list format.
redfin:/data/local/tmp \# simpleperf inject --output branch-list -o branch_list.data
```
Converting ETM data to AutoFDO input files needs to read binaries.
So for userspace libraries, they can be converted on device. For kernel, it needs
to be converted on host, with vmlinux and kernel modules available.
Convert ETM data for userspace libraries:
```sh
# Injecting ETM data on device. It writes output to perf_inject.data.
# perf_inject.data is a text file, containing branch counts for each library.
redfin:/data/local/tmp \# simpleperf inject -i branch_list.data
```
Convert ETM data for kernel:
```sh
# pull ETM data to host.
host $ adb pull /data/local/tmp/branch_list.data
# download vmlinux and kernel modules to <binary_dir>
# host simpleperf is in <aosp-top>/system/extras/simpleperf/scripts/bin/linux/x86_64/simpleperf,
# or you can build simpleperf by `mmma system/extras/simpleperf`.
host $ simpleperf inject --symdir <binary_dir> -i branch_list.data
```
The generated perf_inject.data may contain branch info for multiple binaries. But AutoFDO only
accepts one at a time. So we need to split perf_inject.data.
The format of perf_inject.data is below:
```perf_inject.data format
executed range with count info for binary1
branch with count info for binary1
// name for binary1
executed range with count info for binary2
branch with count info for binary2
// name for binary2
...
```
We need to split perf_inject.data, and make sure one file only contains info for one binary.
Then we can use [AutoFDO](https://github.com/google/autofdo) to create profile. AutoFDO only works
for binaries having an executable segment as its first loadable segment. But binaries built in
Android may not follow this rule. Simpleperf inject command knows how to work around this problem.
But there is a check in AutoFDO forcing binaries to start with an executable segment. We need to
disable the check in AutoFDO, by commenting out L127-L136 in
https://github.com/google/autofdo/commit/188db2834ce74762ed17108ca344916994640708#diff-2d132ecbb5e4f13e0da65419f6d1759dd27d6b696786dd7096c0c34d499b1710R127-R136.
Then we can use `create_llvm_prof` in AutoFDO to create profiles used by clang.
```sh
# perf_inject_binary1.data is split from perf_inject.data, and only contains branch info for binary1.
host $ autofdo/create_llvm_prof -profile perf_inject_binary1.data -profiler text -binary path_of_binary1 -out a.prof -format binary
# perf_inject_kernel.data is split from perf_inject.data, and only contains branch info for [kernel.kallsyms].
host $ autofdo/create_llvm_prof -profile perf_inject_kernel.data -profiler text -binary vmlinux -out a.prof -format binary
```
Then we can use a.prof for PGO during compilation, via `-fprofile-sample-use=a.prof`.
[Here](https://clang.llvm.org/docs/UsersManual.html#using-sampling-profilers) are more details.
### A complete example: etm_test_loop.cpp
`etm_test_loop.cpp` is an example to show the complete process.
The source code is in [etm_test_loop.cpp](https://android.googlesource.com/platform/system/extras/+/main/simpleperf/runtest/etm_test_loop.cpp).
The build script is in [Android.bp](https://android.googlesource.com/platform/system/extras/+/main/simpleperf/runtest/Android.bp).
It builds an executable called `etm_test_loop`, which runs on device.
Step 1: Build `etm_test_loop` binary.
```sh
(host) <AOSP>$ . build/envsetup.sh
(host) <AOSP>$ lunch aosp_arm64-trunk_staging-userdebug
(host) <AOSP>$ make etm_test_loop
```
Step 2: Run `etm_test_loop` on device, and collect ETM data for its running.
```sh
(host) <AOSP>$ adb push out/target/product/generic_arm64/system/bin/etm_test_loop /data/local/tmp
(host) <AOSP>$ adb root
(host) <AOSP>$ adb shell
(device) / # cd /data/local/tmp
(device) /data/local/tmp # chmod a+x etm_test_loop
(device) /data/local/tmp # simpleperf record -e cs-etm:u ./etm_test_loop
simpleperf I cmd_record.cpp:729] Recorded for 0.0370068 seconds. Start post processing.
simpleperf I cmd_record.cpp:799] Aux data traced: 1689136
(device) /data/local/tmp # simpleperf inject -i perf.data --output branch-list -o branch_list.data
simpleperf W dso.cpp:557] failed to read min virtual address of [vdso]: File not found
(device) /data/local/tmp # exit
(host) <AOSP>$ adb pull /data/local/tmp/branch_list.data
```
Step 3: Convert ETM data to AutoFDO data.
```sh
# Build simpleperf tool on host.
(host) <AOSP>$ make simpleperf_ndk
(host) <AOSP>$ simpleperf_ndk64 inject -i branch_list.data -o perf_inject_etm_test_loop.data --symdir out/target/product/generic_arm64/symbols/system/bin
simpleperf W cmd_inject.cpp:505] failed to build instr ranges for binary [vdso]: File not found
(host) <AOSP>$ cat perf_inject_etm_test_loop.data
13
1000-1010:1
1014-1050:1
...
112c->0:1
// /data/local/tmp/etm_test_loop
(host) <AOSP>$ create_llvm_prof -profile perf_inject_etm_test_loop.data -profiler text -binary out/target/product/generic_arm64/symbols/system/bin/etm_test_loop -out etm_test_loop.afdo -format binary
(host) <AOSP>$ ls -lh etm_test_loop.afdo
rw-r--r-- 1 user group 241 Aug 29 16:04 etm_test_loop.afdo
```
Step 4: Use AutoFDO data to build optimized binary.
```sh
(host) <AOSP>$ mkdir toolchain/pgo-profiles/sampling/
(host) <AOSP>$ cp etm_test_loop.afdo toolchain/pgo-profiles/sampling/
(host) <AOSP>$ vi toolchain/pgo-profiles/sampling/Android.bp
# edit Android.bp to add a fdo_profile module
# soong_namespace {}
#
# fdo_profile {
# name: "etm_test_loop_afdo",
# profile: ["etm_test_loop.afdo"],
# }
```
`soong_namespace` is added to support fdo_profile modules with the same name
In a product config mk file, update `PRODUCT_AFDO_PROFILES` with
```make
PRODUCT_AFDO_PROFILES += etm_test_loop://toolchain/pgo-profiles/sampling:etm_test_loop_afdo
```
```sh
(host) <AOSP>$ vi system/extras/simpleperf/runtest/Android.bp
# edit Android.bp to enable afdo for etm_test_loop.
# cc_binary {
# name: "etm_test_loop",
# srcs: ["etm_test_loop.cpp"],
# afdo: true,
# }
(host) <AOSP>$ make etm_test_loop
```
If comparing the disassembly of `out/target/product/generic_arm64/symbols/system/bin/etm_test_loop`
before and after optimizing with AutoFDO data, we can see different preferences when branching.
## Collect ETM data with a daemon
Android also has a daemon collecting ETM data periodically. It only runs on userdebug and eng
devices. The source code is in https://android.googlesource.com/platform/system/extras/+/main/profcollectd/.
## Support ETM in the kernel
To let simpleperf use ETM function, we need to enable Coresight driver in the kernel, which lives in
`<linux_kernel>/drivers/hwtracing/coresight`.
The Coresight driver can be enabled by below kernel configs:
```config
CONFIG_CORESIGHT=y
CONFIG_CORESIGHT_LINK_AND_SINK_TMC=y
CONFIG_CORESIGHT_SOURCE_ETM4X=y
```
On Kernel 5.10+, we recommend building Coresight driver as kernel modules. Because it works with
GKI kernel.
```config
CONFIG_CORESIGHT=m
CONFIG_CORESIGHT_LINK_AND_SINK_TMC=m
CONFIG_CORESIGHT_SOURCE_ETM4X=m
```
Android common kernel 5.10+ should have all the Coresight patches needed to collect ETM data.
Android common kernel 5.4 misses two patches. But by adding patches in
https://android-review.googlesource.com/q/topic:test_etm_on_hikey960_5.4, we can collect ETM data
on hikey960 with 5.4 kernel.
For Android common kernel 4.14 and 4.19, we have backported all necessary Coresight patches.
Besides Coresight driver, we also need to add Coresight devices in device tree. An example is in
https://github.com/torvalds/linux/blob/master/arch/arm64/boot/dts/arm/juno-base.dtsi. There should
be a path flowing ETM data from ETM device through funnels, ETF and replicators, all the way to
ETR, which writes ETM data to system memory.
One optional flag in ETM device tree is "arm,coresight-loses-context-with-cpu". It saves ETM
registers when a CPU enters low power state. It may be needed to avoid
"coresight_disclaim_device_unlocked" warning when doing system wide collection.
One optional flag in ETR device tree is "arm,scatter-gather". Simpleperf requests 4M system memory
for ETR to store ETM data. Without IOMMU, the memory needs to be contiguous. If the kernel can't
fulfill the request, simpleperf will report out of memory error. Fortunately, we can use
"arm,scatter-gather" flag to let ETR run in scatter gather mode, which uses non-contiguous memory.
### A possible problem: trace_id mismatch
Each CPU has an ETM device, which has a unique trace_id assigned from the kernel.
The formula is: `trace_id = 0x10 + cpu * 2`, as in https://github.com/torvalds/linux/blob/master/include/linux/coresight-pmu.h#L37.
If the formula is modified by local patches, then simpleperf inject command can't parse ETM data
properly and is likely to give empty output.
## Enable ETM in the bootloader
Unless ARMv8.4 Self-hosted Trace extension is implemented, ETM is considered as an external debug
interface. It may be disabled by fuse (like JTAG). So we need to check if ETM is disabled, and
if bootloader provides a way to reenable it.
We can tell if ETM is disable by checking its TRCAUTHSTATUS register, which is exposed in sysfs,
like /sys/bus/coresight/devices/coresight-etm0/mgmt/trcauthstatus. To reenable ETM, we need to
enable non-Secure non-invasive debug on ARM CPU. The method depends on chip vendors(SOCs).
## Related docs
* [Arm Architecture Reference Manual Armv8, D3 AArch64 Self-hosted Trace](https://developer.arm.com/documentation/ddi0487/latest)
* [ARM ETM Architecture Specification](https://developer.arm.com/documentation/ihi0064/latest/)
* [ARM CoreSight Architecture Specification](https://developer.arm.com/documentation/ihi0029/latest)
* [CoreSight Components Technical Reference Manual](https://developer.arm.com/documentation/ddi0314/h/)
* [CoreSight Trace Memory Controller Technical Reference Manual](https://developer.arm.com/documentation/ddi0461/b/)
* [OpenCSD library for decoding ETM data](https://github.com/Linaro/OpenCSD)
* [AutoFDO tool for converting profile data](https://github.com/google/autofdo)

View File

@ -0,0 +1,79 @@
# Debug dwarf unwinding
Dwarf unwinding is the default way of getting call graphs in simpleperf. In this process,
simpleperf asks the kernel to add stack and register data to each sample. Then it uses
[libunwindstack](https://cs.android.com/android/platform/superproject/+/main:system/unwinding/libunwindstack/)
to unwind the call stack. libunwindstack uses dwarf sections (like .debug_frame or .eh_frame) in
elf files to know how to unwind the stack.
By default, `simpleperf record` unwinds a sample before saving it to disk, to reduce space consumed
by stack data. But this behavior makes it harder to reproduce unwinding problems. So we added
debug-unwind command, to help debug and profile dwarf unwinding. Below are two use cases.
[TOC]
## Debug failed unwinding cases
Unwinding a sample can fail for different reasons: not enough stack or register data, unknown
thread maps, no dwarf info, bugs in code, etc. And to fix them, we need to get error details
and be able to reproduce them. simpleperf record cmd has two options for this:
`--keep-failed-unwinding-result` keeps error code for failed unwinding samples. It's lightweight
and gives us a brief idea why unwinding stops.
`--keep-failed-unwinding-debug-info` keeps stack and register data for failed unwinding samples. It
can be used to reproduce the unwinding process given proper elf files. Below is an example.
```sh
# Run record cmd and keep failed unwinding debug info.
$ simpleperf64 record --app com.example.android.displayingbitmaps -g --duration 10 \
--keep-failed-unwinding-debug-info
...
simpleperf I cmd_record.cpp:762] Samples recorded: 22026. Samples lost: 0.
# Generate a text report containing failed unwinding cases.
$ simpleperf debug-unwind --generate-report -o report.txt
# Pull report.txt on host and show it using debug_unwind_reporter.py.
# Show summary.
$ debug_unwind_reporter.py -i report.txt --summary
# Show summary of samples failed at a symbol.
$ debug_unwind_reporter.py -i report.txt --summary --include-end-symbol SocketInputStream_socketRead0
# Show details of samples failed at a symbol.
$ debug_unwind_reporter.py -i report.txt --include-end-symbol SocketInputStream_socketRead0
# Reproduce unwinding a failed case.
$ simpleperf debug-unwind --unwind-sample --sample-time 256666343213301
# Generate a test file containing a failed case and elf files for debugging it.
$ simpleperf debug-unwind --generate-test-file --sample-time 256666343213301 --keep-binaries-in-test-file \
/apex/com.android.runtime/lib64/bionic/libc.so,/apex/com.android.art/lib64/libopenjdk.so -o test.data
```
## Profile unwinding process
We can also record samples without unwinding them. Then we can use debug-unwind cmd to unwind the
samples after recording. Below is an example.
```sh
# Record samples without unwinding them.
$ simpleperf record --app com.example.android.displayingbitmaps -g --duration 10 \
--no-unwind
...
simpleperf I cmd_record.cpp:762] Samples recorded: 9923. Samples lost: 0.
# Use debug-unwind cmd to unwind samples.
$ simpleperf debug-unwind --unwind-sample
```
We can profile the unwinding process, get hot functions for improvement.
```sh
# Profile debug-unwind cmd.
$ simpleperf record -g -o perf_unwind.data simpleperf debug-unwind --unwind-sample --skip-sample-print
# Then pull perf_unwind.data and report it.
$ report_html.py -i perf_unwind.data
# We can also add source code annotation in report.html.
$ binary_cache_builder.py -i perf_unwind.data -lib <path to aosp-main>/out/target/product/<device-name>/symbols/system
$ report_html.py -i perf_unwind.data --add_source_code --source_dirs <path to aosp-main>/system/
```

View File

@ -0,0 +1,696 @@
# Executable commands reference
[TOC]
## How simpleperf works
Modern CPUs have a hardware component called the performance monitoring unit (PMU). The PMU has
several hardware counters, counting events like how many cpu cycles have happened, how many
instructions have executed, or how many cache misses have happened.
The Linux kernel wraps these hardware counters into hardware perf events. In addition, the Linux
kernel also provides hardware independent software events and tracepoint events. The Linux kernel
exposes all events to userspace via the perf_event_open system call, which is used by simpleperf.
Simpleperf has three main commands: stat, record and report.
The stat command gives a summary of how many events have happened in the profiled processes in a
time period. Heres how it works:
1. Given user options, simpleperf enables profiling by making a system call to the kernel.
2. The kernel enables counters while the profiled processes are running.
3. After profiling, simpleperf reads counters from the kernel, and reports a counter summary.
The record command records samples of the profiled processes in a time period. Heres how it works:
1. Given user options, simpleperf enables profiling by making a system call to the kernel.
2. Simpleperf creates mapped buffers between simpleperf and the kernel.
3. The kernel enables counters while the profiled processes are running.
4. Each time a given number of events happen, the kernel dumps a sample to the mapped buffers.
5. Simpleperf reads samples from the mapped buffers and stores profiling data in a file called
perf.data.
The report command reads perf.data and any shared libraries used by the profiled processes,
and outputs a report showing where the time was spent.
## Commands
Simpleperf supports several commands, listed below:
```
The debug-unwind command: debug/test dwarf based offline unwinding, used for debugging simpleperf.
The dump command: dumps content in perf.data, used for debugging simpleperf.
The help command: prints help information for other commands.
The kmem command: collects kernel memory allocation information (will be replaced by Python scripts).
The list command: lists all event types supported on the Android device.
The record command: profiles processes and stores profiling data in perf.data.
The report command: reports profiling data in perf.data.
The report-sample command: reports each sample in perf.data, used for supporting integration of
simpleperf in Android Studio.
The stat command: profiles processes and prints counter summary.
```
Each command supports different options, which can be seen through help message.
```sh
# List all commands.
$ simpleperf --help
# Print help message for record command.
$ simpleperf record --help
```
Below describes the most frequently used commands, which are list, stat, record and report.
## The list command
The list command lists all events available on the device. Different devices may support different
events because they have different hardware and kernels.
```sh
$ simpleperf list
List of hw-cache events:
branch-loads
...
List of hardware events:
cpu-cycles
instructions
...
List of software events:
cpu-clock
task-clock
...
```
On ARM/ARM64, the list command also shows a list of raw events, they are the events supported by
the ARM PMU on the device. The kernel has wrapped part of them into hardware events and hw-cache
events. For example, raw-cpu-cycles is wrapped into cpu-cycles, raw-instruction-retired is wrapped
into instructions. The raw events are provided in case we want to use some events supported on the
device, but unfortunately not wrapped by the kernel.
## The stat command
The stat command is used to get event counter values of the profiled processes. By passing options,
we can select which events to use, which processes/threads to monitor, how long to monitor and the
print interval.
```sh
# Stat using default events (cpu-cycles,instructions,...), and monitor process 7394 for 10 seconds.
$ simpleperf stat -p 7394 --duration 10
Performance counter statistics:
# count event_name # count / runtime
16,513,564 cpu-cycles # 1.612904 GHz
4,564,133 stalled-cycles-frontend # 341.490 M/sec
6,520,383 stalled-cycles-backend # 591.666 M/sec
4,900,403 instructions # 612.859 M/sec
47,821 branch-misses # 6.085 M/sec
25.274251(ms) task-clock # 0.002520 cpus used
4 context-switches # 158.264 /sec
466 page-faults # 18.438 K/sec
Total test time: 10.027923 seconds.
```
### Select events to stat
We can select which events to use via -e.
```sh
# Stat event cpu-cycles.
$ simpleperf stat -e cpu-cycles -p 11904 --duration 10
# Stat event cache-references and cache-misses.
$ simpleperf stat -e cache-references,cache-misses -p 11904 --duration 10
```
When running the stat command, if the number of hardware events is larger than the number of
hardware counters available in the PMU, the kernel shares hardware counters between events, so each
event is only monitored for part of the total time. As a result, the number of events shown is
smaller than the number of events that actually happened. The following is an example.
```sh
# Stat using event cache-references, cache-references:u,....
$ simpleperf stat -p 7394 -e cache-references,cache-references:u,cache-references:k \
-e cache-misses,cache-misses:u,cache-misses:k,instructions --duration 1
Performance counter statistics:
# count event_name # count / runtime
490,713 cache-references # 151.682 M/sec
899,652 cache-references:u # 130.152 M/sec
855,218 cache-references:k # 111.356 M/sec
61,602 cache-misses # 7.710 M/sec
33,282 cache-misses:u # 5.050 M/sec
11,662 cache-misses:k # 4.478 M/sec
0 instructions #
Total test time: 1.000867 seconds.
simpleperf W cmd_stat.cpp:946] It seems the number of hardware events are more than the number of
available CPU PMU hardware counters. That will trigger hardware counter
multiplexing. As a result, events are not counted all the time processes
running, and event counts are smaller than what really happens.
Use --print-hw-counter to show available hardware counters.
```
In the example above, we monitor 7 events. Each event is only monitored part of the total time.
Because the number of cache-references is smaller than the number of cache-references:u
(cache-references only in userspace) and cache-references:k (cache-references only in kernel).
The number of instructions is zero. After printing the result, simpleperf checks if CPUs have
enough hardware counters to count hardware events at the same time. If not, it prints a warning.
To avoid hardware counter multiplexing, we can use `simpleperf stat --print-hw-counter` to show
available counters on each CPU. Then don't monitor more hardware events than counters available.
```sh
$ simpleperf stat --print-hw-counter
There are 2 CPU PMU hardware counters available on cpu 0.
There are 2 CPU PMU hardware counters available on cpu 1.
There are 2 CPU PMU hardware counters available on cpu 2.
There are 2 CPU PMU hardware counters available on cpu 3.
There are 2 CPU PMU hardware counters available on cpu 4.
There are 2 CPU PMU hardware counters available on cpu 5.
There are 2 CPU PMU hardware counters available on cpu 6.
There are 2 CPU PMU hardware counters available on cpu 7.
```
When counter multiplexing happens, there is no guarantee of which events will be monitored at
which time. If we want to ensure some events are always monitored at the same time, we can use
`--group`.
```sh
# Stat using event cache-references, cache-references:u,....
$ simpleperf stat -p 7964 --group cache-references,cache-misses \
--group cache-references:u,cache-misses:u --group cache-references:k,cache-misses:k \
--duration 1
Performance counter statistics:
# count event_name # count / runtime
2,088,463 cache-references # 181.360 M/sec
47,871 cache-misses # 2.292164% miss rate
1,277,600 cache-references:u # 136.419 M/sec
25,977 cache-misses:u # 2.033265% miss rate
326,305 cache-references:k # 74.724 M/sec
13,596 cache-misses:k # 4.166654% miss rate
Total test time: 1.029729 seconds.
simpleperf W cmd_stat.cpp:946] It seems the number of hardware events are more than the number of
...
```
### Select target to stat
We can select which processes or threads to monitor via -p or -t. Monitoring a
process is the same as monitoring all threads in the process. Simpleperf can also fork a child
process to run the new command and then monitor the child process.
```sh
# Stat process 11904 and 11905.
$ simpleperf stat -p 11904,11905 --duration 10
# Stat processes with name containing "chrome".
$ simpleperf stat -p chrome --duration 10
# Stat processes with name containing part matching regex "chrome:(privileged|sandboxed)".
$ simpleperf stat -p "chrome:(privileged|sandboxed)" --duration 10
# Stat thread 11904 and 11905.
$ simpleperf stat -t 11904,11905 --duration 10
# Start a child process running `ls`, and stat it.
$ simpleperf stat ls
# Stat the process of an Android application. On non-root devices, this only works for debuggable
# or profileable from shell apps.
$ simpleperf stat --app simpleperf.example.cpp --duration 10
# Stat only selected thread 11904 in an app.
$ simpleperf stat --app simpleperf.example.cpp -t 11904 --duration 10
# Stat system wide using -a.
$ simpleperf stat -a --duration 10
```
### Decide how long to stat
When monitoring existing threads, we can use --duration to decide how long to monitor. When
monitoring a child process running a new command, simpleperf monitors until the child process ends.
In this case, we can use Ctrl-C to stop monitoring at any time.
```sh
# Stat process 11904 for 10 seconds.
$ simpleperf stat -p 11904 --duration 10
# Stat until the child process running `ls` finishes.
$ simpleperf stat ls
# Stop monitoring using Ctrl-C.
$ simpleperf stat -p 11904 --duration 10
^C
```
If you want to write a script to control how long to monitor, you can send one of SIGINT, SIGTERM,
SIGHUP signals to simpleperf to stop monitoring.
### Decide the print interval
When monitoring perf counters, we can also use --interval to decide the print interval.
```sh
# Print stat for process 11904 every 300ms.
$ simpleperf stat -p 11904 --duration 10 --interval 300
# Print system wide stat at interval of 300ms for 10 seconds. Note that system wide profiling needs
# root privilege.
$ su 0 simpleperf stat -a --duration 10 --interval 300
```
### Display counters in systrace
Simpleperf can also work with systrace to dump counters in the collected trace. Below is an example
to do a system wide stat.
```sh
# Capture instructions (kernel only) and cache misses with interval of 300 milliseconds for 15
# seconds.
$ su 0 simpleperf stat -e instructions:k,cache-misses -a --interval 300 --duration 15
# On host launch systrace to collect trace for 10 seconds.
(HOST)$ external/chromium-trace/systrace.py --time=10 -o new.html sched gfx view
# Open the collected new.html in browser and perf counters will be shown up.
```
### Show event count per thread
By default, stat cmd outputs an event count sum for all monitored targets. But when `--per-thread`
option is used, stat cmd outputs an event count for each thread in monitored targets. It can be
used to find busy threads in a process or system wide. With `--per-thread` option, stat cmd opens
a perf_event_file for each exisiting thread. If a monitored thread creates new threads, event
count for new threads will be added to the monitored thread by default, otherwise omitted if
`--no-inherit` option is also used.
```sh
# Print event counts for each thread in process 11904. Event counts for threads created after
# stat cmd will be added to threads creating them.
$ simpleperf stat --per-thread -p 11904 --duration 1
# Print event counts for all threads running in the system every 1s. Threads not running will not
# be reported.
$ su 0 simpleperf stat --per-thread -a --interval 1000 --interval-only-values
# Print event counts for all threads running in the system every 1s. Event counts for threads
# created after stat cmd will be omitted.
$ su 0 simpleperf stat --per-thread -a --interval 1000 --interval-only-values --no-inherit
```
### Show event count per core
By default, stat cmd outputs an event count sum for all monitored cpu cores. But when `--per-core`
option is used, stat cmd outputs an event count for each core. It can be used to see how events
are distributed on different cores.
When stating non-system wide with `--per-core` option, simpleperf creates a perf event for each
monitored thread on each core. When a thread is in running state, perf events on all cores are
enabled, but only the perf event on the core running the thread is in running state. So the
percentage comment shows runtime_on_a_core / runtime_on_all_cores. Note that, percentage is still
affected by hardware counter multiplexing. Check simpleperf log output for ways to distinguish it.
```sh
# Print event counts for each cpu running threads in process 11904.
# A percentage shows runtime_on_a_cpu / runtime_on_all_cpus.
$ simpleperf stat -e cpu-cycles --per-core -p 1057 --duration 3
Performance counter statistics:
# cpu count event_name # count / runtime
0 1,667,660 cpu-cycles # 1.571565 GHz
1 3,850,440 cpu-cycles # 1.736958 GHz
2 2,463,792 cpu-cycles # 1.701367 GHz
3 2,350,528 cpu-cycles # 1.700841 GHz
5 7,919,520 cpu-cycles # 2.377081 GHz
6 105,622,673 cpu-cycles # 2.381331 GHz
Total test time: 3.002703 seconds.
# Print event counts for each cpu system wide.
$ su 0 simpleperf stat --per-core -a --duration 1
# Print cpu-cycle event counts for each cpu for each thread running in the system.
$ su 0 simpleperf stat -e cpu-cycles -a --per-thread --per-core --duration 1
```
### Monitor different events on different cores
Android devices usually have big and little cores. Different cores may support different events.
Therefore, we may want to monitor different events on different cores. We can do this using
the `--cpu` option. The `--cpu` option selects the cores on which to monitor events. A `--cpu`
option affects all the following events until meeting another `--cpu` option. The first `--cpu`
option also affects all events before it. Following are some examples:
```sh
# By default, cpu-cycles and instructions are monitored on all cpus.
$ su 0 simpleperf stat -e cpu-cycles,instructions -a --duration 1 --per-core
# Use one `--cpu` option to monitor cpu-cycles and instructions only on cpu 0-3,8.
$ su 0 simpleperf stat -e cpu-cycles --cpu 0-3,8 -e instructions -a --duration 1 --per-core
# Use two `--cpu` options to monitor raw-l3d-cache-refill-rd on cpu 0-3, and raw-l3d-cache-refill on
# cpu 4-8.
$ su 0 simpleperf stat --cpu 0-3 -e raw-l3d-cache-refill-rd --cpu 4-8 -e raw-l3d-cache-refill \
-a --duration 1 --per-core
```
## The record command
The record command is used to dump samples of the profiled processes. Each sample can contain
information like the time at which the sample was generated, the number of events since last
sample, the program counter of a thread, the call chain of a thread.
By passing options, we can select which events to use, which processes/threads to monitor,
what frequency to dump samples, how long to monitor, and where to store samples.
```sh
# Record on process 7394 for 10 seconds, using default event (cpu-cycles), using default sample
# frequency (4000 samples per second), writing records to perf.data.
$ simpleperf record -p 7394 --duration 10
simpleperf I cmd_record.cpp:316] Samples recorded: 21430. Samples lost: 0.
```
### Select events to record
By default, the cpu-cycles event is used to evaluate consumed cpu cycles. But we can also use other
events via -e.
```sh
# Record using event instructions.
$ simpleperf record -e instructions -p 11904 --duration 10
# Record using task-clock, which shows the passed CPU time in nanoseconds.
$ simpleperf record -e task-clock -p 11904 --duration 10
```
### Select target to record
The way to select target in record command is similar to that in the stat command.
```sh
# Record process 11904 and 11905.
$ simpleperf record -p 11904,11905 --duration 10
# Record processes with name containing "chrome".
$ simpleperf record -p chrome --duration 10
# Record processes with name containing part matching regex "chrome:(privileged|sandboxed)".
$ simpleperf record -p "chrome:(privileged|sandboxed)" --duration 10
# Record thread 11904 and 11905.
$ simpleperf record -t 11904,11905 --duration 10
# Record a child process running `ls`.
$ simpleperf record ls
# Record the process of an Android application. On non-root devices, this only works for debuggable
# or profileable from shell apps.
$ simpleperf record --app simpleperf.example.cpp --duration 10
# Record only selected thread 11904 in an app.
$ simpleperf record --app simpleperf.example.cpp -t 11904 --duration 10
# Record system wide.
$ simpleperf record -a --duration 10
```
### Set the frequency to record
We can set the frequency to dump records via -f or -c. For example, -f 4000 means
dumping approximately 4000 records every second when the monitored thread runs. If a monitored
thread runs 0.2s in one second (it can be preempted or blocked in other times), simpleperf dumps
about 4000 * 0.2 / 1.0 = 800 records every second. Another way is using -c. For example, -c 10000
means dumping one record whenever 10000 events happen.
```sh
# Record with sample frequency 1000: sample 1000 times every second running.
$ simpleperf record -f 1000 -p 11904,11905 --duration 10
# Record with sample period 100000: sample 1 time every 100000 events.
$ simpleperf record -c 100000 -t 11904,11905 --duration 10
```
To avoid taking too much time generating samples, kernel >= 3.10 sets the max percent of cpu time
used for generating samples (default is 25%), and decreases the max allowed sample frequency when
hitting that limit. Simpleperf uses --cpu-percent option to adjust it, but it needs either root
privilege or to be on Android >= Q.
```sh
# Record with sample frequency 10000, with max allowed cpu percent to be 50%.
$ simpleperf record -f 1000 -p 11904,11905 --duration 10 --cpu-percent 50
```
### Decide how long to record
The way to decide how long to monitor in record command is similar to that in the stat command.
```sh
# Record process 11904 for 10 seconds.
$ simpleperf record -p 11904 --duration 10
# Record until the child process running `ls` finishes.
$ simpleperf record ls
# Stop monitoring using Ctrl-C.
$ simpleperf record -p 11904 --duration 10
^C
```
If you want to write a script to control how long to monitor, you can send one of SIGINT, SIGTERM,
SIGHUP signals to simpleperf to stop monitoring.
### Set the path to store profiling data
By default, simpleperf stores profiling data in perf.data in the current directory. But the path
can be changed using -o.
```sh
# Write records to data/perf2.data.
$ simpleperf record -p 11904 -o data/perf2.data --duration 10
```
#### Record call graphs
A call graph is a tree showing function call relations. Below is an example.
```
main() {
FunctionOne();
FunctionTwo();
}
FunctionOne() {
FunctionTwo();
FunctionThree();
}
a call graph:
main-> FunctionOne
| |
| |-> FunctionTwo
| |-> FunctionThree
|
|-> FunctionTwo
```
A call graph shows how a function calls other functions, and a reversed call graph shows how
a function is called by other functions. To show a call graph, we need to first record it, then
report it.
There are two ways to record a call graph, one is recording a dwarf based call graph, the other is
recording a stack frame based call graph. Recording dwarf based call graphs needs support of debug
information in native binaries. While recording stack frame based call graphs needs support of
stack frame registers.
```sh
# Record a dwarf based call graph
$ simpleperf record -p 11904 -g --duration 10
# Record a stack frame based call graph
$ simpleperf record -p 11904 --call-graph fp --duration 10
```
[Here](README.md#suggestions-about-recording-call-graphs) are some suggestions about recording call graphs.
### Record both on CPU time and off CPU time
Simpleperf is a CPU profiler, which generates samples for a thread only when it is running on a
CPU. But sometimes we want to know where the thread time is spent off-cpu (like preempted by other
threads, blocked in IO or waiting for some events). To support this, simpleperf added a
--trace-offcpu option to the record command. When --trace-offcpu is used, simpleperf does the
following things:
1) Only cpu-clock/task-clock event is allowed to be used with --trace-offcpu. This let simpleperf
generate on-cpu samples for cpu-clock event.
2) Simpleperf also monitors sched:sched_switch event, which will generate a sched_switch sample
each time the monitored thread is scheduled off cpu.
3) Simpleperf also records context switch records. So it knows when the thread is scheduled back on
a cpu.
The samples and context switch records collected by simpleperf for a thread are shown below:
![simpleperf_trace_offcpu_sample_mode](simpleperf_trace_offcpu_sample_mode.png)
Here we have two types of samples:
1) on-cpu samples generated for cpu-clock event. The period value in each sample means how many
nanoseconds are spent on cpu (for the callchain of this sample).
2) off-cpu (sched_switch) samples generated for sched:sched_switch event. The period value is
calculated as **Timestamp of the next switch on record** minus **Timestamp of the current sample**
by simpleperf. So the period value in each sample means how many nanoseconds are spent off cpu
(for the callchain of this sample).
**note**: In reality, switch on records and samples may lost. To mitigate the loss of accuracy, we
calculate the period of an off-cpu sample as **Timestamp of the next switch on record or sample**
minus **Timestamp of the current sample**.
When reporting via python scripts, simpleperf_report_lib.py provides SetTraceOffCpuMode() method
to control how to report the samples:
1) on-cpu mode: only report on-cpu samples.
2) off-cpu mode: only report off-cpu samples.
3) on-off-cpu mode: report both on-cpu and off-cpu samples, which can be split by event name.
4) mixed-on-off-cpu mode: report on-cpu and off-cpu samples under the same event name.
If not set, mixed-on-off-cpu mode will be used to report.
When using report_html.py, inferno and report_sample.py, the report mode can be set by
--trace-offcpu option.
Below are some examples recording and reporting trace offcpu profiles.
```sh
# Check if --trace-offcpu is supported by the kernel (should be available on kernel >= 4.2).
$ simpleperf list --show-features
trace-offcpu
...
# Record with --trace-offcpu.
$ simpleperf record -g -p 11904 --duration 10 --trace-offcpu -e cpu-clock
# Record system wide with --trace-offcpu.
$ simpleperf record -a -g --duration 3 --trace-offcpu -e cpu-clock
# Record with --trace-offcpu using app_profiler.py.
$ ./app_profiler.py -p com.google.samples.apps.sunflower \
-r "-g -e cpu-clock:u --duration 10 --trace-offcpu"
# Report on-cpu samples.
$ ./report_html.py --trace-offcpu on-cpu
# Report off-cpu samples.
$ ./report_html.py --trace-offcpu off-cpu
# Report on-cpu and off-cpu samples under different event names.
$ ./report_html.py --trace-offcpu on-off-cpu
# Report on-cpu and off-cpu samples under the same event name.
$ ./report_html.py --trace-offcpu mixed-on-off-cpu
```
## The report command
The report command is used to report profiling data generated by the record command. The report
contains a table of sample entries. Each sample entry is a row in the report. The report command
groups samples belong to the same process, thread, library, function in the same sample entry. Then
sort the sample entries based on the event count a sample entry has.
By passing options, we can decide how to filter out uninteresting samples, how to group samples
into sample entries, and where to find profiling data and binaries.
Below is an example. Records are grouped into 4 sample entries, each entry is a row. There are
several columns, each column shows piece of information belonging to a sample entry. The first
column is Overhead, which shows the percentage of events inside the current sample entry in total
events. As the perf event is cpu-cycles, the overhead is the percentage of CPU cycles used in each
function.
```sh
# Reports perf.data, using only records sampled in libsudo-game-jni.so, grouping records using
# thread name(comm), process id(pid), thread id(tid), function name(symbol), and showing sample
# count for each row.
$ simpleperf report --dsos /data/app/com.example.sudogame-2/lib/arm64/libsudo-game-jni.so \
--sort comm,pid,tid,symbol -n
Cmdline: /data/data/com.example.sudogame/simpleperf record -p 7394 --duration 10
Arch: arm64
Event: cpu-cycles (type 0, config 0)
Samples: 28235
Event count: 546356211
Overhead Sample Command Pid Tid Symbol
59.25% 16680 sudogame 7394 7394 checkValid(Board const&, int, int)
20.42% 5620 sudogame 7394 7394 canFindSolution_r(Board&, int, int)
13.82% 4088 sudogame 7394 7394 randomBlock_r(Board&, int, int, int, int, int)
6.24% 1756 sudogame 7394 7394 @plt
```
### Set the path to read profiling data
By default, the report command reads profiling data from perf.data in the current directory.
But the path can be changed using -i.
```sh
$ simpleperf report -i data/perf2.data
```
### Set the path to find binaries
To report function symbols, simpleperf needs to read executable binaries used by the monitored
processes to get symbol table and debug information. By default, the paths are the executable
binaries used by monitored processes while recording. However, these binaries may not exist when
reporting or not contain symbol table and debug information. So we can use --symfs to redirect
the paths.
```sh
# In this case, when simpleperf wants to read executable binary /A/b, it reads file in /A/b.
$ simpleperf report
# In this case, when simpleperf wants to read executable binary /A/b, it prefers file in
# /debug_dir/A/b to file in /A/b.
$ simpleperf report --symfs /debug_dir
# Read symbols for system libraries built locally. Note that this is not needed since Android O,
# which ships symbols for system libraries on device.
$ simpleperf report --symfs $ANDROID_PRODUCT_OUT/symbols
```
### Filter samples
When reporting, it happens that not all records are of interest. The report command supports four
filters to select samples of interest.
```sh
# Report records in threads having name sudogame.
$ simpleperf report --comms sudogame
# Report records in process 7394 or 7395
$ simpleperf report --pids 7394,7395
# Report records in thread 7394 or 7395.
$ simpleperf report --tids 7394,7395
# Report records in libsudo-game-jni.so.
$ simpleperf report --dsos /data/app/com.example.sudogame-2/lib/arm64/libsudo-game-jni.so
```
### Group samples into sample entries
The report command uses --sort to decide how to group sample entries.
```sh
# Group records based on their process id: records having the same process id are in the same
# sample entry.
$ simpleperf report --sort pid
# Group records based on their thread id and thread comm: records having the same thread id and
# thread name are in the same sample entry.
$ simpleperf report --sort tid,comm
# Group records based on their binary and function: records in the same binary and function are in
# the same sample entry.
$ simpleperf report --sort dso,symbol
# Default option: --sort comm,pid,tid,dso,symbol. Group records in the same thread, and belong to
# the same function in the same binary.
$ simpleperf report
```
#### Report call graphs
To report a call graph, please make sure the profiling data is recorded with call graphs,
as [here](#record-call-graphs).
```
$ simpleperf report -g
```

View File

@ -0,0 +1,109 @@
# Inferno
![logo](./inferno_small.png)
[TOC]
## Description
Inferno is a flamegraph generator for native (C/C++) Android apps. It was
originally written to profile and improve surfaceflinger performance
(Android compositor) but it can be used for any native Android application
. You can see a sample report generated with Inferno
[here](./report.html). Report are self-contained in HTML so they can be
exchanged easily.
Notice there is no concept of time in a flame graph since all callstack are
merged together. As a result, the width of a flamegraph represents 100% of
the number of samples and the height is related to the number of functions on
the stack when sampling occurred.
![flamegraph sample](./main_thread_flamegraph.png)
In the flamegraph featured above you can see the main thread of SurfaceFlinger.
It is immediatly apparent that most of the CPU time is spent processing messages
`android::SurfaceFlinger::onMessageReceived`. The most expensive task is to ask
the screen to be refreshed as `android::DisplayDevice::prepare` shows in orange
. This graphic division helps to see what part of the program is costly and
where a developer's effort to improve performances should go.
## Example of bottleneck
A flamegraph give you instant vision on the CPU cycles cost centers but
it can also be used to find specific offenders. To find them, look for
plateaus. It is easier to see an example:
![flamegraph sample](./bottleneck.png)
In the previous flamegraph, two
plateaus (due to `android::BufferQueueCore::validateConsistencyLocked`)
are immediately apparent.
## How it works
Inferno relies on simpleperf to record the callstack of a native application
thousands of times per second. Simpleperf takes care of unwinding the stack
either using frame pointer (recommended) or dwarf. At the end of the recording
`simpleperf` also symbolize all IPs automatically. The record are aggregated and
dumps dumped to a file `perf.data`. This file is pulled from the Android device
and processed on the host by Inferno. The callstacks are merged together to
visualize in which part of an app the CPU cycles are spent.
## How to use it
Open a terminal and from `simpleperf/scripts` directory type:
```
./inferno.sh (on Linux/Mac)
inferno.bat (on Windows)
```
Inferno will collect data, process them and automatically open your web browser
to display the HTML report.
## Parameters
You can select how long to sample for, the color of the node and many other
things. Use `-h` to get a list of all supported parameters.
```
./inferno.sh -h
```
## Troubleshooting
### Messy flame graph
A healthy flame graph features a single call site at its base (see [here](./report.html)).
If you don't see a unique call site like `_start` or `_start_thread` at the base
from which all flames originate, something went wrong. : Stack unwinding may
fail to reach the root callsite. These incomplete
callstack are impossible to merge properly. By default Inferno asks
`simpleperf` to unwind the stack via the kernel and frame pointers. Try to
perform unwinding with dwarf `-du`, you can further tune this setting.
### No flames
If you see no flames at all or a mess of 1 level flame without a common base,
this may be because you compiled without frame pointers. Make sure there is no
` -fomit-frame-pointer` in your build config. Alternatively, ask simpleperf to
collect data with dward unwinding `-du`.
### High percentage of lost samples
If simpleperf reports a lot of lost sample it is probably because you are
unwinding with `dwarf`. Dwarf unwinding involves copying the stack before it is
processed. Try to use frame pointer unwinding which can be done by the kernel
and it much faster.
The cost of frame pointer is negligible on arm64 parameter but considerable
on arm 32-bit arch (due to register pressure). Use a 64-bit build for better
profiling.
### run-as: package not debuggable
If you cannot run as root, make sure the app is debuggable otherwise simpleperf
will not be able to profile it.

Binary file not shown.

After

Width:  |  Height:  |  Size: 309 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Some files were not shown because too many files have changed in this diff Show More