For each possible p-code operation, we give a brief description and provide a table that lists the inputs that must be present and their meaning. We also list the basic syntax for denoting the operation when describing semantics in a processor specification file.
Copy a sequence of contiguous bytes from anywhere to anywhere. Size of input0 and output must be the same.
This instruction loads data from a dynamic location into the output variable by dereferencing a pointer. The “pointer” comes in two pieces. One piece, input1, is a normal variable containing the offset of the object being pointed at. The other piece, input0, is a constant indicating the space into which the offset applies. The data in input1 is interpreted as an unsigned offset and should have the same size as the space referred to by the ID, i.e. a 4-byte address space requires a 4-byte offset. The space ID is not manually entered by a user but is automatically generated by the p-code compiler. The amount of data loaded by this instruction is determined by the size of the output variable. It is easy to confuse the address space of the output and input1 variables and the Address Space represented by the ID, which could all be different. Unlike many programming models, there are multiple spaces that a “pointer” can refer to, and so an extra ID is required.
It is possible for the addressable unit of an address space to be bigger than a single byte. If the wordsize attribute of the space given by the ID is bigger than one, the offset into the space obtained from input1 must be multiplied by this value in order to obtain the correct byte offset into the space.
This instruction is the complement of LOAD. The data in the variable input2 is stored at a dynamic location by dereferencing a pointer. As with LOAD, the “pointer” comes in two pieces: a space ID part, and an offset variable. The size of input1 must match the address space specified by the ID, and the amount of data stored is determined by the size of input2.
Its possible for the addressable unit of an address space to be bigger than a single byte. If the wordsize attribute of the space given by the ID is bigger than one, the offset into the space obtained from input1 must be multiplied by this value in order to obtain the correct byte offset into the space.
This is an absolute jump instruction. The varnode parameter input0 encodes the destination address (address space and offset) of the jump. The varnode is not treated as a variable for this instruction and does not store the destination. Its address space and offset are the destination. The size of input0 is irrelevant.
Confusion about the meaning of this instruction can result because of the translation from machine instructions to p-code. The destination of the jump is a machine address and refers to the machine instruction at that address. When attempting to determine which p-code instruction is executed next, the rule is: execute the first p-code instruction resulting from the translation of the machine instruction(s) at that address. The resulting p-code instruction may not be attached directly to the indicated address due to NOP instructions and delay slots.
If input0 is constant, i.e. its address space is the constant address space, then it encodes a p-code relative branch. In this case, the offset of input0 is considered a relative offset into the indexed list of p-code operations corresponding to the translation of the current machine instruction. This allows branching within the operations forming a single instruction. For example, if the BRANCH occurs as the pcode operation with index 5 for the instruction, it can branch to operation with index 8 by specifying a constant destination “address” of 3. Negative constants can be used for backward branches.
This is a conditional branch instruction where the dynamic condition for taking the branch is determined by the 1 byte variable input1. If this variable is non-zero, the condition is considered true and the branch is taken. As in the BRANCH instruction the parameter input0 is not treated as a variable but as an address and is interpreted in the same way. Furthermore, a constant space address is also interpreted as a relative address so that a CBRANCH can do p-code relative branching. See the discussion for the BRANCH operation.
This is an indirect branching instruction. The address to branch to is determined dynamically (at runtime) by examining the contents of the variable input0. As this instruction is currently defined, the variable input0 only contains the offset of the destination, and the address space is taken from the address associated with the branching instruction itself. So execution can only branch within the same address space via this instruction. The size of the variable input0 must match the size of offsets for the current address space. P-code relative branching is not possible with BRANCHIND.
This instruction is semantically equivalent to the BRANCH instruction. Beware: This instruction does not behave like a typical function call. In particular, there is no internal stack in p-code for saving the return address. Use of this instruction instead of BRANCH is intended to provide a hint to algorithms that try to follow code flow. It indicates that the original machine instruction, of which this p-code instruction is only a part, is intended to be a function call. The p-code instruction does not implement the full semantics of the call itself; it only implements the final branch.
In the raw p-code translation process, this operation can only take input0, but in follow-on analysis, it can take arbitrary additional inputs. These represent (possibly partial) recovery of the parameters being passed to the logical call represented by this operation. These additional parameters have no effect on the original semantics of the raw p-code but naturally hold the varnode values flowing into the call.
This instruction is semantically equivalent to the BRANCHIND instruction. It does not perform a function call in the usual sense of the term. It merely indicates that the original machine instruction is intended to be an indirect call. See the discussion for the CALL instruction.
As with the CALL instruction, this operation may take additional inputs when not in raw form, representing the parameters being passed to the logical call.
This instruction is semantically equivalent to the BRANCHIND instruction. It does not perform a return from subroutine in the usual sense of the term. It merely indicates that the original machine instruction is intended to be a return from subroutine. See the discussion for the CALL instruction.
Similarly to CALL and CALLIND, this operation may take an additional input when not in raw form. If input1 is present it represents the value being returned by this operation. This is used by analysis algorithms to hold the value logically flowing back to the parent subroutine.
This is a concatenation operator that understands the endianness of the data. The size of input0 and input1 must add up to the size of output. The data from the inputs is concatenated in such a way that, if the inputs and output are considered integers, the first input makes up the most significant part of the output.
This is a truncation operator that understands the endianness of the data. Input1 indicates the number of least significant bytes of input0 to be thrown away. Output is then filled with any remaining bytes of input0 up to the size of output. If the size of output is smaller than the size of input0 minus the constant input1, then the additional most significant bytes of input0 will also be truncated.
This is a bit count (population count) operator. Within the binary representation of the value contained in the input varnode, the number of 1 bits are counted and then returned in the output varnode. A value of 0 returns 0, a 4-byte varnode containing the value 232-1 (all bits set) returns 32, for instance. The input and output varnodes can have any size. The resulting count is zero extended into the output varnode.
This operator counts the number of zeros starting at the most significant bit. For instance, for a 4-byte varnode, a value of 0 returns 32, a value of 1 returns 31, and the value 231 returns 0. The resulting count is zero extended into the output varnode.
This is the integer equality operator. Output is assigned true, if input0 equals input1. It works for signed, unsigned, or any contiguous data where the match must be down to the bit. Both inputs must be the same size, and the output must have a size of 1.
This is the integer inequality operator. Output is assigned true, if input0 does not equal input1. It works for signed, unsigned, or any contiguous data where the match must be down to the bit. Both inputs must be the same size, and the output must have a size of 1.
This is an unsigned integer comparison operator. If the unsigned integer input0 is strictly less than the unsigned integer input1, output is set to true. Both inputs must be the same size, and the output must have a size of 1.
This is a signed integer comparison operator. If the signed integer input0 is strictly less than the signed integer input1, output is set to true. Both inputs must be the same size, and the output must have a size of 1.
This is an unsigned integer comparison operator. If the unsigned integer input0 is less than or equal to the unsigned integer input1, output is set to true. Both inputs must be the same size, and the output must have a size of 1.
This is a signed integer comparison operator. If the signed integer input0 is less than or equal to the signed integer input1, output is set to true. Both inputs must be the same size, and the output must have a size of 1.
Zero-extend the data in input0 and store the result in output. Copy all the data from input0 into the least significant positions of output. Fill out any remaining space in the most significant bytes of output with zero. The size of output must be strictly bigger than the size of input.
Sign-extend the data in input0 and store the result in output. Copy all the data from input0 into the least significant positions of output. Fill out any remaining space in the most significant bytes of output with either zero or all ones (0xff) depending on the most significant bit of input0. The size of output must be strictly bigger than the size of input0.
This is standard integer addition. It works for either unsigned or signed interpretations of the integer encoding (twos complement). Size of both inputs and output must be the same. The addition is of course performed modulo this size. Overflow and carry conditions are calculated by other operations. See INT_CARRY and INT_SCARRY.
This is standard integer subtraction. It works for either unsigned or signed interpretations of the integer encoding (twos complement). Size of both inputs and output must be the same. The subtraction is of course performed modulo this size. Overflow and borrow conditions are calculated by other operations. See INT_SBORROW and INT_LESS.
This operation checks for unsigned addition overflow or carry conditions. If the result of adding input0 and input1 as unsigned integers overflows the size of the varnodes, output is assigned true. Both inputs must be the same size, and output must be size 1.
This operation checks for signed addition overflow or carry conditions. If the result of adding input0 and input1 as signed integers overflows the size of the varnodes, output is assigned true. Both inputs must be the same size, and output must be size 1.
This operation checks for signed subtraction overflow or borrow conditions. If the result of subtracting input1 from input0 as signed integers overflows the size of the varnodes, output is assigned true. Both inputs must be the same size, and output must be size 1. Note that the equivalent unsigned subtraction overflow condition is INT_LESS.
This is the twos complement or arithmetic negation operation. Treating input0 as a signed integer, the result is the same integer value but with the opposite sign. This is equivalent to doing a bitwise negation of input0 and then adding one. Both input0 and output must be the same size.
This is the bitwise negation operation. Output is the result of taking every bit of input0 and flipping it. Both input0 and output must be the same size.
This operation performs a logical Exclusive-Or on the bits of input0 and input1. Both inputs and output must be the same size.
This operation performs a Logical-And on the bits of input0 and input1. Both inputs and output must be the same size.
This operation performs a Logical-Or on the bits of input0 and input1. Both inputs and output must be the same size.
This operation performs a left shift on input0. The value given by input1, interpreted as an unsigned integer, indicates the number of bits to shift. The vacated (least significant) bits are filled with zero. If input1 is zero, no shift is performed and input0 is copied into output. If input1 is larger than the number of bits in output, the result is zero. Both input0 and output must be the same size. Input1 can be any size.
This operation performs an unsigned (logical) right shift on input0. The value given by input1, interpreted as an unsigned integer, indicates the number of bits to shift. The vacated (most significant) bits are filled with zero. If input1 is zero, no shift is performed and input0 is copied into output. If input1 is larger than the number of bits in output, the result is zero. Both input0 and output must be the same size. Input1 can be any size.
This operation performs a signed (arithmetic) right shift on input0. The value given by input1, interpreted as an unsigned integer, indicates the number of bits to shift. The vacated bits are filled with the original value of the most significant (sign) bit of input0. If input1 is zero, no shift is performed and input0 is copied into output. If input1 is larger than the number of bits in output, the result is zero or all 1-bits (-1), depending on the original sign of input0. Both input0 and output must be the same size. Input1 can be any size.
This is an integer multiplication operation. The result of multiplying input0 and input1, viewed as integers, is stored in output. Both inputs and output must be the same size. The multiplication is performed modulo the size, and the result is true for either a signed or unsigned interpretation of the inputs and output. To get extended precision results, the inputs must first by zero-extended or sign-extended to the desired size.
This is an unsigned integer division operation. Divide input0 by input1, truncating the result to the nearest integer, and store the result in output. Both inputs and output must be the same size. There is no handling of division by zero. To simulate a processor’s handling of a division-by-zero trap, other operations must be used before the INT_DIV.
This is an unsigned integer remainder operation. The remainder of performing the unsigned integer division of input0 and input1 is put in output. Both inputs and output must be the same size. If q = input0/input1, using the INT_DIV operation defined above, then output satisfies the equation q*input1 + output = input0, using the INT_MULT and INT_ADD operations.
This is a signed integer division operation. The resulting integer is the one closest to the rational value input0/input1 but which is still smaller in absolute value. Both inputs and output must be the same size. There is no handling of division by zero. To simulate a processor’s handling of a division-by-zero trap, other operations must be used before the INT_SDIV.
This is a signed integer remainder operation. The remainder of performing the signed integer division of input0 and input1 is put in output. Both inputs and output must be the same size. If q = input0 s/ input1, using the INT_SDIV operation defined above, then output satisfies the equation q*input1 + output = input0, using the INT_MULT and INT_ADD operations.
This is a logical negate operator, where we assume input0 and output are boolean values. It puts the logical complement of input0, treated as a single bit, into output. Both input0 and output are size 1. Boolean values are implemented with a full byte, but are still considered to only support a value of true or false.
This is an Exclusive-Or operator, where we assume the inputs and output are boolean values. It puts the exclusive-or of input0 and input1, treated as single bits, into output. Both inputs and output are size 1. Boolean values are implemented with a full byte, but are still considered to only support a value of true or false.
This is a Logical-And operator, where we assume the inputs and output are boolean values. It puts the logical-and of input0 and input1, treated as single bits, into output. Both inputs and output are size 1. Boolean values are implemented with a full byte, but are still considered to only support a value of true or false.
This is a Logical-Or operator, where we assume the inputs and output are boolean values. It puts the logical-or of input0 and input1, treated as single bits, into output. Both inputs and output are size 1. Boolean values are implemented with a full byte, but are still considered to only support a value of true or false.
This is a floating-point equality operator. Output is assigned true, if input0 and input1 are considered equal as floating-point values. Both inputs must be the same size, and output is size 1. If either input is NaN, output is set to false.
This is a floating-point inequality operator. Output is assigned true, if input0 and input1 are not considered equal as floating-point values. Both inputs must be the same size, and output is size 1. If either input is NaN, output is set to false.
This is a comparison operator, where both inputs are considered floating-point values. Output is assigned true, if input0 is less than input1. Both inputs must be the same size, and output is size 1. If either input is NaN, output is set to false.
This is a comparison operator, where both inputs are considered floating-point values. Output is assigned true, if input0 is less than or equal to input1. Both inputs must be the same size, and output is size 1. If either input is NaN, output is set to false.
This is a floating-point addition operator. The result of adding input0 and input1 as floating-point values is stored in output. Both inputs and output must be the same size. If either input is NaN, output is set to NaN. If any overflow condition occurs, output is set to NaN.
This is a floating-point subtraction operator. The result of subtracting input1 from input0 as floating-point values is stored in output. Both inputs and output must be the same size. If either input is NaN, output is set to NaN. If any overflow condition occurs, output is set to NaN.
This is a floating-point multiplication operator. The result of multiplying input0 to input1 as floating-point values is stored in output. Both inputs and output must be the same size. If either input is NaN, output is set to NaN. If any overflow condition occurs, output is set to NaN.
This is a floating-point division operator. The result of dividing input1 into input0 as floating-point values is stored in output. Both inputs and output must be the same size. If either input is NaN, output is set to NaN. If any overflow condition occurs, output is set to NaN.
This is a floating-point negation operator. The floating-point value in input0 is stored in output with the opposite sign. Both input and output must be the same size. If input is NaN, output is set to NaN.
This is a floating-point absolute-value operator. The absolute value of input0 is stored in output. Both input0 and output must be the same size. If input0 is NaN, output is set to NaN.
This is a floating-point square-root operator. The square root of input0 is stored in output. Both input0 and output must be the same size. If input0 is NaN, output is set to NaN.
This operation rounds input0, as a signed floating-point value, towards positive infinity. For instance, the value 1.2 rounds to 2.0; -1.2 rounds to -1.0. The integral value obtained by rounding input0 up is stored in output, as a floating-point value. Both input0 and output must be the same size. If input0 is NaN, output is set to NaN.
This operation rounds input0, as a floating-point value, towards negative infinity. For instance, the value 1.2 rounds to 1.0 and -1.2 rounds to -2.0. The integral value obtained by rounding input0 down is stored in output, as a floating-point value. FLOAT_FLOOR does not produce a twos complement integer output (See the TRUNC operator). Both input0 and output must be the same size. If input0 is NaN, output is set to NaN.
This is a floating-point rounding operator. The integral value closest to the floating-point value in input0 is stored in output, as a floating-point value. For example, 1.2 rounds to 1.0 and 1.9 rounds to 2.0. FLOAT_ROUND does not produce a twos complement integer output (See the TRUNC operator). Both input0 and output must be the same size. If input0 is NaN, output is set to NaN.
This operator returns true in output if input0 is interpreted as NaN. Output must be size 1, and input0 can be any size.
This is an integer to floating-point conversion operator. Input0 viewed as a signed integer is converted to floating-point format and stored in output. Input0 and output do not need to be the same size. The conversion to floating-point may involve a loss of precision.
This is a floating-point precision conversion operator. The floating-point value in input0 is converted to a floating-point value of a different size and stored in output. If output is smaller than input0, then the operation will lose precision. Input0 and output should be different sizes. If input0 is NaN, then output is set to NaN.
This is a floating-point to integer conversion operator. The floating-point value in input0 is converted to a signed integer and stored in output using the default twos complement encoding. The fractional part of input0 is dropped in the conversion by rounding towards zero. Input0 and output can be different sizes.