Skip to content

Commit

Permalink
Add build checks for portable OpenFlow structure padding and alignment.
Browse files Browse the repository at this point in the history
This causes the build to fail with an error message if openflow.h contains
a structure whose members are not aligned in a portable way.
  • Loading branch information
blp committed Jan 25, 2010
1 parent 84a0ee8 commit 05b3c97
Show file tree
Hide file tree
Showing 12 changed files with 347 additions and 7 deletions.
1 change: 0 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@
/aclocal.m4
/autom4te.cache
/build-arch-stamp
/build-aux
/build-indep-stamp
/compile
/config.guess
Expand Down
4 changes: 4 additions & 0 deletions build-aux/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
/compile
/depcomp
/install-sh
/missing
267 changes: 267 additions & 0 deletions build-aux/check-structs
Original file line number Diff line number Diff line change
@@ -0,0 +1,267 @@
#! /usr/bin/python

import sys
import re

macros = {}

anyWarnings = False

types = {}
types['char'] = {"size": 1, "alignment": 1}
types['uint8_t'] = {"size": 1, "alignment": 1}
types['uint16_t'] = {"size": 2, "alignment": 2}
types['uint32_t'] = {"size": 4, "alignment": 4}
types['uint64_t'] = {"size": 8, "alignment": 8}

token = None
line = ""
idRe = "[a-zA-Z_][a-zA-Z_0-9]*"
tokenRe = "#?" + idRe + "|[0-9]+|."
inComment = False
inDirective = False
def getToken():
global token
global line
global inComment
global inDirective
while True:
line = line.lstrip()
if line != "":
if line.startswith("/*"):
inComment = True
line = line[2:]
elif inComment:
commentEnd = line.find("*/")
if commentEnd < 0:
line = ""
else:
inComment = False
line = line[commentEnd + 2:]
else:
match = re.match(tokenRe, line)
token = match.group(0)
line = line[len(token):]
if token.startswith('#'):
inDirective = True
elif token in macros and not inDirective:
line = macros[token] + line
continue
return True
elif inDirective:
token = "$"
inDirective = False
return True
else:
global lineNumber
line = inputFile.readline()
lineNumber += 1
while line.endswith("\\\n"):
line = line[:-2] + inputFile.readline()
lineNumber += 1
if line == "":
if token == None:
fatal("unexpected end of input")
token = None
return False

def fatal(msg):
sys.stderr.write("%s:%d: error at \"%s\": %s\n" % (fileName, lineNumber, token, msg))
sys.exit(1)

def warn(msg):
global anyWarnings
anyWarnings = True
sys.stderr.write("%s:%d: warning: %s\n" % (fileName, lineNumber, msg))

def skipDirective():
getToken()
while token != '$':
getToken()

def isId(s):
return re.match(idRe + "$", s) != None

def forceId():
if not isId(token):
fatal("identifier expected")

def forceInteger():
if not re.match('[0-9]+$', token):
fatal("integer expected")

def match(t):
if token == t:
getToken()
return True
else:
return False

def forceMatch(t):
if not match(t):
fatal("%s expected" % t)

def parseTaggedName():
assert token in ('struct', 'union')
name = token
getToken()
forceId()
name = "%s %s" % (name, token)
getToken()
return name

def parseTypeName():
if token in ('struct', 'union'):
name = parseTaggedName()
elif isId(token):
name = token
getToken()
else:
fatal("type name expected")

if name in types:
return name
else:
fatal("unknown type \"%s\"" % name)

def parseStruct():
isStruct = token == 'struct'
structName = parseTaggedName()
if token == ";":
return

ofs = size = 0
alignment = 4 # ARM has minimum 32-bit alignment
forceMatch('{')
while not match('}'):
typeName = parseTypeName()
typeSize = types[typeName]['size']
typeAlignment = types[typeName]['alignment']

forceId()
memberName = token
getToken()

if match('['):
if token == ']':
count = 0
else:
forceInteger()
count = int(token)
getToken()
forceMatch(']')
else:
count = 1

nBytes = typeSize * count
if isStruct:
if ofs % typeAlignment:
shortage = typeAlignment - (ofs % typeAlignment)
warn("%s member %s is %d bytes short of %d-byte alignment"
% (structName, memberName, shortage, typeAlignment))
size += shortage
ofs += shortage
size += nBytes
ofs += nBytes
else:
if nBytes > size:
size = nBytes
if typeAlignment > alignment:
alignment = typeAlignment

forceMatch(';')
if size % alignment:
shortage = alignment - (size % alignment)
if (structName == "struct ofp_packet_in" and
shortage == 2 and
memberName == 'data' and
count == 0):
# This is intentional
pass
else:
warn("%s needs %d bytes of tail padding" % (structName, shortage))
size += shortage
types[structName] = {"size": size, "alignment": alignment}

def checkStructs():
if len(sys.argv) < 2:
sys.stderr.write("at least one non-option argument required; "
"use --help for help")
sys.exit(1)

if '--help' in sys.argv:
argv0 = sys.argv[0]
slash = argv0.rfind('/')
if slash:
argv0 = argv0[slash + 1:]
print '''\
%(argv0)s, for checking struct and struct member alignment
usage: %(argv0)s HEADER [HEADER]...
This program reads the header files specified on the command line and
verifies that all struct members are aligned on natural boundaries
without any need for the compiler to add additional padding. It also
verifies that each struct's size is a multiple of 32 bits (because
some ABIs for ARM require all structs to be a multiple of 32 bits), or
64 bits if the struct has any 64-bit members, again without the
compiler adding additional padding. Finally, it checks struct size
assertions using OFP_ASSERT.
Header files are read in the order specified. #include directives are
not processed, so specify them in dependency order.
This program is specialized for reading include/openflow/openflow.h
and include/openflow/nicira-ext.h. It will not work on arbitrary
header files without extensions.''' % {"argv0": argv0}
sys.exit(0)

global fileName
for fileName in sys.argv[1:]:
global inputFile
global lineNumber
inputFile = open(fileName)
lineNumber = 0
while getToken():
if token in ("#ifdef", "#ifndef", "#include",
"#endif", "#elif", "#else"):
skipDirective()
elif token == "#define":
getToken()
name = token
if line.startswith('('):
skipDirective()
else:
definition = ""
getToken()
while token != '$':
definition += token
getToken()
macros[name] = definition
elif token == "enum":
while token != ';':
getToken()
elif token in ('struct', 'union'):
parseStruct()
elif match('OFP_ASSERT') or match('BOOST_STATIC_ASSERT'):
forceMatch('(')
forceMatch('sizeof')
forceMatch('(')
typeName = parseTypeName()
forceMatch(')')
forceMatch('=')
forceMatch('=')
forceInteger()
size = int(token)
getToken()
forceMatch(')')
if types[typeName]['size'] != size:
warn("%s is %d bytes long but declared as %d" % (
typeName, types[typeName]['size'], size))
else:
fatal("parse error")
inputFile.close()
if anyWarnings:
sys.exit(1)

if __name__ == '__main__':
checkStructs()
5 changes: 4 additions & 1 deletion configure.ac
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Copyright (c) 2008, 2009 Nicira Networks
# Copyright (c) 2008, 2009, 2010 Nicira Networks
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -88,4 +88,7 @@ datapath/linux-2.6/Makefile
datapath/linux-2.6/Makefile.main
tests/atlocal])

dnl This makes sure that include/openflow gets created in the build directory.
AC_CONFIG_COMMANDS([include/openflow/openflow.h.stamp])

AC_OUTPUT
1 change: 1 addition & 0 deletions include/openflow/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
/openflow.h.stamp
16 changes: 16 additions & 0 deletions include/openflow/automake.mk
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,19 @@ noinst_HEADERS += \
include/openflow/openflow-mgmt.h \
include/openflow/nicira-ext.h \
include/openflow/openflow.h

if HAVE_PYTHON
all-local: include/openflow/openflow.h.stamp
include/openflow/openflow.h.stamp: \
include/openflow/openflow.h build-aux/check-structs
$(PYTHON) $(srcdir)/build-aux/check-structs $(srcdir)/include/openflow/openflow.h
touch $@

all-local: include/openflow/nicira-ext.h.stamp
include/openflow/nicira-ext.h.stamp: include/openflow/openflow.h include/openflow/nicira-ext.h build-aux/check-structs
$(PYTHON) $(srcdir)/build-aux/check-structs $(srcdir)/include/openflow/openflow.h $(srcdir)/include/openflow/nicira-ext.h
touch $@
endif

EXTRA_DIST += build-aux/check-structs
DISTCLEANFILES += include/openflow/openflow.h.stamp
4 changes: 2 additions & 2 deletions include/openflow/nicira-ext.h
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2008, 2009 Nicira Networks
* Copyright (c) 2008, 2009, 2010 Nicira Networks
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -65,7 +65,7 @@ struct nicira_header {
uint32_t vendor; /* NX_VENDOR_ID. */
uint32_t subtype; /* One of NXT_* above. */
};
OFP_ASSERT(sizeof(struct nicira_header) == sizeof(struct ofp_vendor_header) + 4);
OFP_ASSERT(sizeof(struct nicira_header) == 16);


enum nx_action_subtype {
Expand Down
7 changes: 6 additions & 1 deletion m4/openvswitch.m4
Original file line number Diff line number Diff line change
Expand Up @@ -256,7 +256,12 @@ else:
done
done
fi])
AC_SUBST([HAVE_PYTHON])
AM_MISSING_PROG([PYTHON], [python])
if test $ovs_cv_python != no; then
PYTHON=$ovs_cv_python
fi])
HAVE_PYTHON=yes
else
HAVE_PYTHON=no
fi
AM_CONDITIONAL([HAVE_PYTHON], [test "$HAVE_PYTHON" = yes])])
6 changes: 4 additions & 2 deletions tests/atlocal.in
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
# -*- shell-script -*-
PERL='@PERL@'
LCOV='@LCOV@'
HAVE_OPENSSL='@HAVE_OPENSSL@'
HAVE_PYTHON='@HAVE_PYTHON@'
LCOV='@LCOV@'
PERL='@PERL@'
PYTHON='@PYTHON@'
1 change: 1 addition & 0 deletions tests/automake.mk
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ TESTSUITE_AT = \
tests/ovsdb-macros.at \
tests/lcov-pre.at \
tests/library.at \
tests/check-structs.at \
tests/daemon.at \
tests/vconn.at \
tests/dir_name.at \
Expand Down
Loading

0 comments on commit 05b3c97

Please sign in to comment.